diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index e6aee6da85f..8f5892206e0 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -15,6 +15,7 @@ set(GAMEENGINE_SRC # Include/Common/BattleHonors.h # Include/Common/BezFwdIterator.h # Include/Common/BezierSegment.h + Include/Common/BIGFileSystem.h # Include/Common/BitFlags.h # Include/Common/BitFlagsIO.h # Include/Common/BorderColors.h @@ -644,6 +645,7 @@ set(GAMEENGINE_SRC Source/Common/System/ArchiveFile.cpp Source/Common/System/ArchiveFileSystem.cpp Source/Common/System/AsciiString.cpp + Source/Common/System/BIGFileSystem.cpp # Source/Common/System/BuildAssistant.cpp # Source/Common/System/CriticalSection.cpp # Source/Common/System/DataChunk.cpp diff --git a/Core/GameEngine/Include/Common/ArchiveFile.h b/Core/GameEngine/Include/Common/ArchiveFile.h index 63987f9c9cb..61a43117017 100644 --- a/Core/GameEngine/Include/Common/ArchiveFile.h +++ b/Core/GameEngine/Include/Common/ArchiveFile.h @@ -47,26 +47,35 @@ class File; class ArchiveFile { public: - ArchiveFile(); - virtual ~ArchiveFile(); + ArchiveFile(AsciiString name, AsciiString path); + ~ArchiveFile(); - virtual Bool getFileInfo( const AsciiString& filename, FileInfo *fileInfo) const = 0; ///< fill in the fileInfo struct with info about the file requested. - virtual File* openFile( const Char *filename, Int access = 0) = 0; ///< Open the specified file within the archive file - virtual void closeAllFiles() = 0; ///< Close all file opened in this archive file - virtual AsciiString getName() = 0; ///< Returns the name of the archive file - virtual AsciiString getPath() = 0; ///< Returns full path and name of archive file - virtual void setSearchPriority( Int new_priority ) = 0; ///< Set this archive file's search priority - virtual void close() = 0; ///< Close this archive file + Bool getFileInfo( const AsciiString& filename, FileInfo *fileInfo) const; ///< fill in the fileInfo struct with info about the file requested. + File* openFile( const Char *filename, Int access = 0); ///< Open the specified file within the archive file + void closeAllFiles(); ///< Close all file opened in this archive file + AsciiString getName(); ///< Returns the name of the archive file + AsciiString getPath(); ///< Returns full path and name of archive file + void setSearchPriority( Int new_priority ); ///< Set this archive file's search priority + void close(); ///< Close this archive file void attachFile(File *file); + void setPriority(Int priority) { m_priority = priority; } + Int getPriority() const { return m_priority; } void getFileListInDirectory(const AsciiString& currentDirectory, const AsciiString& originalDirectory, const AsciiString& searchName, FilenameList &filenameList, Bool searchSubdirectories) const; - void getFileListInDirectory(const DetailedArchivedDirectoryInfo *dirInfo, const AsciiString& currentDirectory, const AsciiString& searchName, FilenameList &filenameList, Bool searchSubdirectories) const; void addFile(const AsciiString& path, const ArchivedFileInfo *fileInfo); ///< add this file to our directory tree. protected: + void getFileListInDirectory(const DetailedArchivedDirectoryInfo *dirInfo, const AsciiString& currentDirectory, const AsciiString& searchName, FilenameList &filenameList, Bool searchSubdirectories) const; const ArchivedFileInfo * getArchivedFileInfo(const AsciiString& filename) const; ///< return the ArchivedFileInfo from the directory tree. +protected: + Int m_priority = ARCHIVE_PRIORITY_RETAIL; ///< lookup priority in the archive file system, lower wins File *m_file; ///< file pointer to the archive file on disk. Kept open so we don't have to continuously open and close the file all the time. + FileInfo m_fileInfo; ///< file info of the archive file on disk. + AsciiString m_name; ///< archive file name + AsciiString m_path; ///< archive file path + +private: DetailedArchivedDirectoryInfo m_rootDirectory; }; diff --git a/Core/GameEngine/Include/Common/ArchiveFileSystem.h b/Core/GameEngine/Include/Common/ArchiveFileSystem.h index 48d144484c6..381765359ac 100644 --- a/Core/GameEngine/Include/Common/ArchiveFileSystem.h +++ b/Core/GameEngine/Include/Common/ArchiveFileSystem.h @@ -53,6 +53,8 @@ #include "Common/AsciiString.h" #include "Common/FileSystem.h" // for typedefs, etc. #include "Common/STLTypedefs.h" +#include +#include //---------------------------------------------------------------------------- // Forward References @@ -120,6 +122,31 @@ class ArchivedFileInfo }; +// Lookup priority of an archive, lower wins. The archive mounted first wins on equal priority. +enum ArchivePriority +{ + ARCHIVE_PRIORITY_EXCLUDED = -1, ///< not mounted: a BIG of a game folder that matches user.exclude_paths + ARCHIVE_PRIORITY_MOD = 0, ///< -mod and -modDir, 0 to 999, a later one is lower and wins + ARCHIVE_PRIORITY_GAME_FOLDER = 1000, ///< BIGs of the Zero Hour folder that are not retail, in name order + ARCHIVE_PRIORITY_GAME_FOLDER_GENERALS = 1100, ///< BIGs of the Generals data (ZH_Generals or the Generals install) that are not retail + ARCHIVE_PRIORITY_USER = 100000, ///< data_packs.user of settings.json, 100000 plus the position in its include_paths + ARCHIVE_PRIORITY_GENERALS_ONLINE = 1000000, ///< data_packs.generals_online, 1000000 plus the position in its include_paths + ARCHIVE_PRIORITY_COMMUNITY_PATCH = 2000000, ///< data_packs.community_patch, 2000000 plus the position in its include_paths + ARCHIVE_PRIORITY_RETAIL = 3000000, ///< Zero Hour retail and official patch BIGs + ARCHIVE_PRIORITY_RETAIL_GENERALS = 3000100 ///< Generals retail and official patch BIGs +}; + +// One entry of the include_paths of a data pack layer in settings.json, see ArchiveFileSystem::parseMountEntry +struct ArchiveMountEntry +{ + enum Kind { KIND_BIG, KIND_BIG_GLOB }; + enum Base { BASE_GAME, BASE_USER, BASE_ABSOLUTE }; + + Kind m_kind; + Base m_base; + AsciiString m_path; ///< with backslashes, relative to the base, the BASE_ABSOLUTE path is complete +}; + class ArchiveFileSystem : public SubsystemInterface { public: @@ -141,12 +168,22 @@ class ArchiveFileSystem : public SubsystemInterface void getFileListInDirectory(const AsciiString& currentDirectory, const AsciiString& originalDirectory, const AsciiString& searchName, FilenameList &filenameList, Bool searchSubdirectories) const; ///< search the given directory for files matching the searchName (egs. *.ini, *.rep). Possibly search subdirectories. Scans each Archive file. Bool getFileInfo(const AsciiString& filename, FileInfo *fileInfo, FileInstance instance = 0) const; ///< see FileSystem.h - virtual Bool loadBigFilesFromDirectory(AsciiString dir, AsciiString fileMask, Bool overwrite = FALSE) = 0; + virtual Bool loadArchiveFilesFromDirectory(AsciiString dir, AsciiString fileMask, Bool overwrite = FALSE) = 0; - // Unprotected this for copy-protection routines - ArchiveFile* getArchiveFile(const AsciiString& filename, FileInstance instance = 0) const; + virtual void loadMods() = 0; + + /// Parses an entry of the include_paths of a data pack layer: "game:", "user:" or an absolute path. A path ending in .big is one BIG, + /// a last component with * or ? and ending in .big is a set of BIGs, a path ending in a slash is every BIG in that folder. + /// Returns FALSE and a reason for an entry that is not valid. + static Bool parseMountEntry(const AsciiString& text, ArchiveMountEntry* entry, AsciiString* reason); + + /// TRUE for the retail archives and the official patches of Zero Hour and Generals, in every language. + static Bool isGeneralsArchivePath(const AsciiString& path, Bool generalsFolder); ///< TRUE for the Generals archives: the Generals install folder, or a ZH_Generals folder + + static Bool isRetailArchive(const AsciiString& path, Bool generalsFolder, Bool zeroHour); - void loadMods(); + /// BIGs of the game folders that are not retail and not excluded by user.exclude_paths, which are mounted above the data pack layers + const std::vector& getGameFolderArchives() const { return m_gameFolderArchives; } ArchivedDirectoryInfo* friend_getArchivedDirectoryInfo(const Char* directory); @@ -160,11 +197,21 @@ class ArchiveFileSystem : public SubsystemInterface AsciiString lastToken; ///< Synonymous for file name if the search directory was a file path }; + ArchiveFile* getArchiveFile(const AsciiString& filename, FileInstance instance = 0) const; ArchivedDirectoryInfoResult getArchivedDirectoryInfo(const Char* directory); - virtual void loadIntoDirectoryTree(ArchiveFile *archiveFile, Bool overwrite = FALSE, Bool sortedByName = FALSE); ///< load the archive file's header information and apply it to the global archive directory tree. sortedByName inserts by archive filename order (addon number convention), ignoring overwrite. + virtual void loadIntoDirectoryTree(ArchiveFile *archiveFile, Int priority = ARCHIVE_PRIORITY_RETAIL); ///< load the archive file's header information and apply it to the global archive directory tree. The lowest priority number wins, the first mounted wins on equal priority. + + Int getNextModPriority() { return m_nextModPriority > ARCHIVE_PRIORITY_MOD ? m_nextModPriority-- : ARCHIVE_PRIORITY_MOD; } ///< -mod and -modDir: a later one wins + Int getGameFolderArchivePriority(const AsciiString& path, Bool generalsFolder, Bool zeroHour); ///< retail archives are the bottom, any other BIG of the game folders ranks above the data pack layers unless user.exclude_paths matches it + + void mountListedArchive(const ArchiveMountEntry& entry, Int priority, const PathExcludeList* excludes); + void mountDataPackLayer(const char* layer, Bool enabled, const std::vector& includePaths, const PathExcludeList* excludes, Int priority); ///< mounts the include_paths of one layer in list order, the first one wins, minus what excludes matches ArchiveFileMap m_archiveFileMap; + std::vector m_gameFolderArchives; + AsciiString m_generalsBaseSource; ///< where the Generals data came from, for the log + Int m_nextModPriority = ARCHIVE_PRIORITY_MOD + 999; ArchivedDirectoryInfo m_rootDirectory; }; diff --git a/Core/GameEngine/Include/Common/AsciiString.h b/Core/GameEngine/Include/Common/AsciiString.h index 66e4982927e..759f72ca913 100644 --- a/Core/GameEngine/Include/Common/AsciiString.h +++ b/Core/GameEngine/Include/Common/AsciiString.h @@ -353,12 +353,12 @@ class AsciiString /** conceptually similar to strtok(): - extract the next seps-delimited token from the front + extract the next separators-delimited token from the front of 'this' and copy it into 'token', returning true if a nonempty token was found. (note that this modifies 'this' as well, stripping the token off!) */ - Bool nextToken(AsciiString* token, const char* seps = nullptr); + Bool nextToken(AsciiString* token, const char* separators = nullptr); /** return true iff the string is "NONE" (case-insensitive). diff --git a/Core/GameEngineDevice/Include/Win32Device/Common/Win32BIGFileSystem.h b/Core/GameEngine/Include/Common/BIGFileSystem.h similarity index 85% rename from Core/GameEngineDevice/Include/Win32Device/Common/Win32BIGFileSystem.h rename to Core/GameEngine/Include/Common/BIGFileSystem.h index 6819ba90577..95e20c0ea6c 100644 --- a/Core/GameEngineDevice/Include/Win32Device/Common/Win32BIGFileSystem.h +++ b/Core/GameEngine/Include/Common/BIGFileSystem.h @@ -22,7 +22,7 @@ // // //////////////////////////////////////////////////////////////////////////////// -//////// Win32BIGFileSystem.h /////////////////////////// +//////// BIGFileSystem.h /////////////////////////// // Bryan Cleveland, August 2002 ///////////////////////////////////////////////////////////// @@ -30,11 +30,11 @@ #include "Common/ArchiveFileSystem.h" -class Win32BIGFileSystem : public ArchiveFileSystem +class BIGFileSystem : public ArchiveFileSystem { public: - Win32BIGFileSystem(); - virtual ~Win32BIGFileSystem() override; + BIGFileSystem(); + virtual ~BIGFileSystem() override; virtual void init() override; virtual void update() override; @@ -49,7 +49,9 @@ class Win32BIGFileSystem : public ArchiveFileSystem virtual void closeArchiveFile(const Char *filename) override; virtual void closeAllFiles() override; ///< Close all files associated with ArchiveFiles - virtual Bool loadBigFilesFromDirectory(AsciiString dir, AsciiString fileMask, Bool overwrite = FALSE) override; + virtual Bool loadArchiveFilesFromDirectory(AsciiString dir, AsciiString fileMask, Bool overwrite = FALSE) override; + + virtual void loadMods() override; protected: }; diff --git a/Core/GameEngine/Include/Common/FileSystem.h b/Core/GameEngine/Include/Common/FileSystem.h index 8a8b99a9f8c..d4a518434d6 100644 --- a/Core/GameEngine/Include/Common/FileSystem.h +++ b/Core/GameEngine/Include/Common/FileSystem.h @@ -53,6 +53,9 @@ #include +#include +#include + #include "WWLib/mutex.h" //---------------------------------------------------------------------------- @@ -109,6 +112,14 @@ typedef UnsignedByte FileInstance; struct FileInfo { + FileInfo() + : sizeHigh(0) + , sizeLow(0) + , timestampHigh(0) + , timestampLow(0) + { + } + Int64 size() const { return (Int64)sizeHigh << 32 | sizeLow; } Int64 timestamp() const { return (Int64)timestampHigh << 32 | timestampLow; } @@ -118,6 +129,30 @@ struct FileInfo { Int timestampLow; }; +//=============================== +// PathExcludeList +//=============================== +/** + * The exclude_paths of settings.json. An entry is game:, user: or an absolute pattern, where * matches any text + * including folders and ? one character. A pattern ending in a slash is a folder and everything in it. Matching ignores case and slash direction. + * A path is tested as a key: game:, user: or the absolute path. + */ +class PathExcludeList +{ +public: + static Bool parse(const std::string& text, std::string* pattern, AsciiString* reason); ///< FALSE and a reason for an entry that is not valid + static std::string makeKey(const Char* path); ///< key of a path that is relative to the game folder or absolute + static std::string makeKey(const Char* path, const Char* userDataPath); ///< same, a path inside the user data folder becomes user: + + void set(const std::vector& entries); ///< entries that are not valid are dropped + Bool isEmpty() const { return m_patterns.empty(); } + Bool matches(const std::string& key) const; + Bool matchesPath(const Char* path) const { return !m_patterns.empty() && matches(makeKey(path)); } + +private: + std::vector m_patterns; +}; + //=============================== // FileSystem //=============================== @@ -157,6 +192,10 @@ class FileSystem : public SubsystemInterface Bool createDirectory(AsciiString directory); ///< create a directory of the given name. + /// Loose files of the game folder that match are treated as if they did not exist: not opened, not found, not listed. Files that are written and files outside the game folder are not affected. + void setExcludedPaths(const std::vector& entries) { m_excludedPaths.set(entries); } + const PathExcludeList& getExcludedPaths() const { return m_excludedPaths; } + static AsciiString normalizePath(const AsciiString& path); ///< normalizes a file path. The path can refer to a directory. File path must be absolute, but does not need to exist. Returns an empty string on failure. static Bool isPathInDirectory(const AsciiString& testPath, const AsciiString& basePath); ///< determines if a file path is within a base path. Both paths must be absolute, but do not need to exist. @@ -164,6 +203,10 @@ class FileSystem : public SubsystemInterface static bool removeExtension(UnicodeString& path); protected: + Bool isLooseFileExcluded(const Char *filename) const { return m_excludedPaths.matchesPath(filename); } + + PathExcludeList m_excludedPaths; + #if ENABLE_FILESYSTEM_EXISTENCE_CACHE struct FileExistData { diff --git a/Core/GameEngine/Include/Common/UnicodeString.h b/Core/GameEngine/Include/Common/UnicodeString.h index 0f3c2333c44..58d73d183ea 100644 --- a/Core/GameEngine/Include/Common/UnicodeString.h +++ b/Core/GameEngine/Include/Common/UnicodeString.h @@ -343,12 +343,12 @@ class UnicodeString /** conceptually similar to strtok(): - extract the next whitespace-delimited token from the front + extract the next separators-delimited token from the front of 'this' and copy it into 'token', returning true if a nonempty token was found. (note that this modifies 'this' as well, stripping the token off!) */ - Bool nextToken(UnicodeString* token, UnicodeString delimiters = UnicodeString::TheEmptyString); + Bool nextToken(UnicodeString* token, const WideChar* separators = nullptr); // // You might think it would be a good idea to overload the * operator diff --git a/Core/GameEngine/Source/Common/System/ArchiveFile.cpp b/Core/GameEngine/Source/Common/System/ArchiveFile.cpp index 1f982b65a1d..ce82fa0b4a8 100644 --- a/Core/GameEngine/Source/Common/System/ArchiveFile.cpp +++ b/Core/GameEngine/Source/Common/System/ArchiveFile.cpp @@ -30,8 +30,12 @@ #include "Common/ArchiveFile.h" #include "Common/ArchiveFileSystem.h" +#include "Common/LocalFileSystem.h" #include "Common/file.h" +#include "Common/LocalFile.h" #include "Common/PerfTimer.h" +#include "Common/RAMFile.h" +#include "Common/StreamingArchiveFile.h" // checks to see if str matches searchString. Search string is done in the @@ -89,8 +93,10 @@ ArchiveFile::~ArchiveFile() } } -ArchiveFile::ArchiveFile() +ArchiveFile::ArchiveFile(AsciiString name, AsciiString path) : m_file(nullptr) + , m_name(name) + , m_path(path) { } @@ -106,13 +112,11 @@ void ArchiveFile::addFile(const AsciiString& path, const ArchivedFileInfo *fileI while (!token.isEmpty()) { DetailedArchivedDirectoryInfoMap::iterator tempiter = dirInfo->m_directories.find(token); - if (tempiter == dirInfo->m_directories.end()) - { + if (tempiter == dirInfo->m_directories.end()) { dirInfo = &(dirInfo->m_directories[token]); dirInfo->m_directoryName = token; } - else - { + else { dirInfo = &tempiter->second; } @@ -132,18 +136,12 @@ void ArchiveFile::getFileListInDirectory(const AsciiString& currentDirectory, co tokenizer.nextToken(&token, "\\/"); while (!token.isEmpty()) { - DetailedArchivedDirectoryInfoMap::const_iterator it = dirInfo->m_directories.find(token); - if (it != dirInfo->m_directories.end()) - { - dirInfo = &it->second; - } - else - { - // if the directory doesn't exist, then there aren't any files to be had. + // if the directory doesn't exist, then there aren't any files to be had. + if (it == dirInfo->m_directories.end()) return; - } + dirInfo = &it->second; tokenizer.nextToken(&token, "\\/"); } @@ -152,34 +150,34 @@ void ArchiveFile::getFileListInDirectory(const AsciiString& currentDirectory, co void ArchiveFile::getFileListInDirectory(const DetailedArchivedDirectoryInfo *dirInfo, const AsciiString& currentDirectory, const AsciiString& searchName, FilenameList &filenameList, Bool searchSubdirectories) const { - DetailedArchivedDirectoryInfoMap::const_iterator diriter = dirInfo->m_directories.begin(); - while (diriter != dirInfo->m_directories.end()) { - const DetailedArchivedDirectoryInfo *tempDirInfo = &(diriter->second); - AsciiString tempdirname; - tempdirname = currentDirectory; - if ((!tempdirname.isEmpty()) && (!tempdirname.endsWith("\\"))) { - tempdirname.concat('\\'); + if (searchSubdirectories) { + DetailedArchivedDirectoryInfoMap::const_iterator diriter = dirInfo->m_directories.begin(); + for (; diriter != dirInfo->m_directories.end(); ++diriter) { + const DetailedArchivedDirectoryInfo *tempDirInfo = &(diriter->second); + AsciiString tempDirName = currentDirectory; + if (!tempDirName.isEmpty() && !tempDirName.endsWith("\\")) { + tempDirName.concat('\\'); + } + tempDirName.concat(tempDirInfo->m_directoryName); + getFileListInDirectory(tempDirInfo, tempDirName, searchName, filenameList, searchSubdirectories); } - tempdirname.concat(tempDirInfo->m_directoryName); - getFileListInDirectory(tempDirInfo, tempdirname, searchName, filenameList, searchSubdirectories); - diriter++; } ArchivedFileInfoMap::const_iterator fileiter = dirInfo->m_files.begin(); - while (fileiter != dirInfo->m_files.end()) { - if (SearchStringMatches(fileiter->second.m_filename, searchName)) { + for (; fileiter != dirInfo->m_files.end(); ++fileiter) { + const ArchivedFileInfo &fileInfo = fileiter->second; + if (SearchStringMatches(fileInfo.m_filename, searchName)) { AsciiString tempfilename; tempfilename = currentDirectory; - if ((!tempfilename.isEmpty()) && (!tempfilename.endsWith("\\"))) { + if (!tempfilename.isEmpty() && !tempfilename.endsWith("\\")) { tempfilename.concat('\\'); } - tempfilename.concat(fileiter->second.m_filename); + tempfilename.concat(fileInfo.m_filename); if (filenameList.find(tempfilename) == filenameList.end()) { // only insert into the list if its not already in there. filenameList.insert(tempfilename); } } - fileiter++; } } @@ -189,7 +187,18 @@ void ArchiveFile::attachFile(File *file) m_file->close(); m_file = nullptr; } + m_file = file; + + if (m_file != nullptr) { + if (!TheLocalFileSystem->getFileInfo(m_file->getName(), &m_fileInfo)) { + DEBUG_CRASH(("ArchiveFile::attachFile - Cannot query file info for '%s' unexpectedly", m_file->getName())); + m_fileInfo = FileInfo(); + } + } + else { + m_fileInfo = FileInfo(); + } } const ArchivedFileInfo * ArchiveFile::getArchivedFileInfo(const AsciiString& filename) const @@ -201,7 +210,7 @@ const ArchivedFileInfo * ArchiveFile::getArchivedFileInfo(const AsciiString& fil tokenizer.toLower(); tokenizer.nextToken(&token, "\\/"); - while (!token.find('.') || tokenizer.find('.')) + while (!tokenizer.isEmpty()) { DetailedArchivedDirectoryInfoMap::const_iterator it = dirInfo->m_directories.find(token); if (it != dirInfo->m_directories.end()) @@ -227,3 +236,116 @@ const ArchivedFileInfo * ArchiveFile::getArchivedFileInfo(const AsciiString& fil } } + +//============================================================================ +// ArchiveFile::openFile +//============================================================================ + +File* ArchiveFile::openFile( const Char *filename, Int access ) +{ + const ArchivedFileInfo *fileInfo = getArchivedFileInfo(AsciiString(filename)); + + if (fileInfo == nullptr) { + return nullptr; + } + + RAMFile *ramFile = nullptr; + + if (BitIsSet(access, File::STREAMING)) + ramFile = newInstance( StreamingArchiveFile ); + else + ramFile = newInstance( RAMFile ); + + ramFile->deleteOnClose(); + if (ramFile->openFromArchive(m_file, fileInfo->m_filename, fileInfo->m_offset, fileInfo->m_size) == FALSE) { + ramFile->close(); + ramFile = nullptr; + return nullptr; + } + + if ((access & File::WRITE) == 0) { + // requesting read only access. Just return the RAM file. + return ramFile; + } + + // whoever is opening this file wants write access, so copy the file to the local disk + // and return that file pointer. + + constexpr size_t bufferSize = 0; + File *localFile = TheLocalFileSystem->openFile(filename, access, bufferSize); + if (localFile != nullptr) { + ramFile->copyDataToFile(localFile); + } + + ramFile->close(); + ramFile = nullptr; + + return localFile; +} + +//============================================================================ +// ArchiveFile::closeAllFiles +//============================================================================ + +void ArchiveFile::closeAllFiles() +{ + +} + +//============================================================================ +// ArchiveFile::getName +//============================================================================ + +AsciiString ArchiveFile::getName() +{ + return m_name; +} + +//============================================================================ +// ArchiveFile::getPath +//============================================================================ + +AsciiString ArchiveFile::getPath() +{ + return m_path; +} + +//============================================================================ +// ArchiveFile::setSearchPriority +//============================================================================ + +void ArchiveFile::setSearchPriority( Int new_priority ) +{ + +} + +//============================================================================ +// ArchiveFile::close +//============================================================================ + +void ArchiveFile::close() +{ + +} + +//============================================================================ +// ArchiveFile::getFileInfo +//============================================================================ + +Bool ArchiveFile::getFileInfo(const AsciiString& filename, FileInfo *fileInfo) const +{ + const ArchivedFileInfo *archivedFileInfo = getArchivedFileInfo(filename); + + if (archivedFileInfo == nullptr) { + return FALSE; + } + + // fill in the size info. Since the size can't be bigger than a JUNK file, the high int will always be 0. + fileInfo->sizeHigh = 0; + fileInfo->sizeLow = archivedFileInfo->m_size; + // the timestamp is taken from the archive file. + fileInfo->timestampHigh = m_fileInfo.timestampHigh; + fileInfo->timestampLow = m_fileInfo.timestampLow; + + return TRUE; +} diff --git a/Core/GameEngine/Source/Common/System/ArchiveFileSystem.cpp b/Core/GameEngine/Source/Common/System/ArchiveFileSystem.cpp index 5720c5b9417..6171f26fabe 100644 --- a/Core/GameEngine/Source/Common/System/ArchiveFileSystem.cpp +++ b/Core/GameEngine/Source/Common/System/ArchiveFileSystem.cpp @@ -46,11 +46,16 @@ //---------------------------------------------------------------------------- #include "PreRTS.h" +#include +#include +#include +#include #include "Common/ArchiveFile.h" #include "Common/ArchiveFileSystem.h" #include "Common/AsciiString.h" #include "Common/LocalFileSystem.h" #include "Common/PerfTimer.h" +#include "Common/Registry.h" #include "../NGMP_include.h" #include "../OnlineServices_Init.h" @@ -127,8 +132,9 @@ ArchiveFileSystem::~ArchiveFileSystem() } } -void ArchiveFileSystem::loadIntoDirectoryTree(ArchiveFile *archiveFile, Bool overwrite, Bool sortedByName) +void ArchiveFileSystem::loadIntoDirectoryTree(ArchiveFile *archiveFile, Int priority) { + archiveFile->setPriority(priority); FilenameList filenameList; @@ -144,9 +150,9 @@ void ArchiveFileSystem::loadIntoDirectoryTree(ArchiveFile *archiveFile, Bool ove AsciiString token; AsciiString tokenizer = *it; tokenizer.toLower(); - Bool infoInPath = tokenizer.nextToken(&token, "\\/"); + tokenizer.nextToken(&token, "\\/"); - while (infoInPath && (!token.find('.') || tokenizer.find('.'))) + while (!tokenizer.isEmpty()) { path.concat(token); path.concat('\\'); @@ -163,119 +169,311 @@ void ArchiveFileSystem::loadIntoDirectoryTree(ArchiveFile *archiveFile, Bool ove dirInfo = &tempiter->second; } - infoInPath = tokenizer.nextToken(&token, "\\/"); + tokenizer.nextToken(&token, "\\/"); } - ArchivedFileLocationMap::iterator fileIt; - if (sortedByName) - { - // Insert by case-insensitive archive filename, matching game folder load order where the alphabetically first archive wins. - const AsciiString baseName = getBaseFilename(archiveFile->getName()); - std::pair range = dirInfo->m_files.equal_range(token); - fileIt = range.first; - while (fileIt != range.second && getBaseFilename(fileIt->second->getName()).compareNoCase(baseName) <= 0) - ++fileIt; - } - else if (overwrite) + // Insert behind every archive of the same or a lower priority number, so the first one mounted wins on equal priority. + // A plain insert goes to the end of the range of equal keys, the range is rebuilt when the new archive belongs in front of others. + std::pair range = dirInfo->m_files.equal_range(token); + std::vector ordered; + for (ArchivedFileLocationMap::iterator rangeIt = range.first; rangeIt != range.second; ++rangeIt) + ordered.push_back(rangeIt->second); + + size_t position = 0; + while (position < ordered.size() && ordered[position]->getPriority() <= priority) + ++position; + + if (position == ordered.size()) { - // When overwriting, try place the new value at the beginning of the key list. - fileIt = dirInfo->m_files.find(token); + dirInfo->m_files.insert(std::make_pair(token, archiveFile)); } else { - // Append to the end of the key list. - fileIt = dirInfo->m_files.end(); + ordered.insert(ordered.begin() + position, archiveFile); + dirInfo->m_files.erase(range.first, range.second); + for (size_t k = 0; k < ordered.size(); ++k) + dirInfo->m_files.insert(std::make_pair(token, ordered[k])); } - dirInfo->m_files.insert(fileIt, std::make_pair(token, archiveFile)); - -#if defined(DEBUG_LOGGING) && ENABLE_FILESYSTEM_LOGGING - { - const stl::const_range range = stl::get_range(dirInfo->m_files, token, 0); - if (range.distance() >= 2) - { - ArchivedFileLocationMap::const_iterator rangeIt0; - ArchivedFileLocationMap::const_iterator rangeIt1; - - if (overwrite) - { - rangeIt0 = range.begin; - rangeIt1 = std::next(rangeIt0); - - DEBUG_LOG(("ArchiveFileSystem::loadIntoDirectoryTree - adding file %s, archived in %s, overwriting same file in %s", - it->str(), - rangeIt0->second->getName().str(), - rangeIt1->second->getName().str() - )); - } - else - { - rangeIt1 = std::prev(range.end); - rangeIt0 = std::prev(rangeIt1); - - DEBUG_LOG(("ArchiveFileSystem::loadIntoDirectoryTree - adding file %s, archived in %s, overwritten by same file in %s", - it->str(), - rangeIt1->second->getName().str(), - rangeIt0->second->getName().str() - )); - } - } - else - { - DEBUG_LOG(("ArchiveFileSystem::loadIntoDirectoryTree - adding file %s, archived in %s", it->str(), archiveFile->getName().str())); - } - } -#endif + DEBUG_LOG(("ArchiveFileSystem::loadIntoDirectoryTree - adding file %s, archived in %s, priority %d", it->str(), archiveFile->getName().str(), priority)); it++; } } -void ArchiveFileSystem::loadMods() +// Retail archive names, without the extension and in lower case. Only the language of the game counts as retail. +static void addRetailNames(std::set& names, Bool zeroHour) +{ + auto add = [&names](const std::string& name) + { + std::string lower = name; + for (char& c : lower) + c = static_cast(tolower(static_cast(c))); + names.insert(lower); + }; + + const std::string language = GetRegistryLanguage().str(); + + if (zeroHour) + { + add(language + "ZH"); + add("Audio" + language + "ZH"); + add("Speech" + language + "ZH"); + add("W3D" + language + "ZH"); + for (const char* name : { "AudioZH", "INIZH", "MapsZH", "Music", "MusicZH", "ShadersZH", "SpeechZH", "TerrainZH", "TexturesZH", "W3DZH", "WindowZH", "gensecZH", + "PatchData", "PatchINI", "PatchWindow", "PatchZH" }) + add(name); + } + else + { + add(language); + add("Audio" + language); + add("Speech" + language); + for (const char* name : { "Audio", "INI", "Music", "Patch", "PatchData", "PatchINI", "PatchWindow", "Speech", "Terrain", "Textures", "W3D", "Window", "gensec", "maps", "shaders" }) + add(name); + } +} + +Bool ArchiveFileSystem::isGeneralsArchivePath(const AsciiString& path, Bool generalsFolder) +{ + if (generalsFolder) + return TRUE; + + AsciiString lowerPath = path; + lowerPath.toLower(); + return strstr(lowerPath.str(), "zh_generals\\") != nullptr || strstr(lowerPath.str(), "zh_generals/") != nullptr; +} + +Bool ArchiveFileSystem::isRetailArchive(const AsciiString& path, Bool generalsFolder, Bool zeroHour) +{ + static std::set zeroHourNames; + static std::set generalsNames; + if (zeroHourNames.empty()) + { + addRetailNames(zeroHourNames, TRUE); + addRetailNames(generalsNames, FALSE); + } + + AsciiString name = getBaseFilename(path); + if (!name.endsWithNoCase(".big")) + return FALSE; + name.truncateBy(4); + std::string lower = name.str(); + for (char& c : lower) + c = static_cast(tolower(static_cast(c))); + + // The Generals archives are in the Generals install folder, or in a ZH_Generals folder inside the game folder + const Bool inGeneralsFolder = isGeneralsArchivePath(path, generalsFolder); + + if (zeroHour && !inGeneralsFolder) + return zeroHourNames.count(lower) != 0; + return generalsNames.count(lower) != 0; +} + +Int ArchiveFileSystem::getGameFolderArchivePriority(const AsciiString& path, Bool generalsFolder, Bool zeroHour) +{ + // The Zero Hour archives rank above the Generals ones in their tier, whatever the order of the scan + const Bool generals = zeroHour && isGeneralsArchivePath(path, generalsFolder); + if (isRetailArchive(path, generalsFolder, zeroHour)) + return generals ? ARCHIVE_PRIORITY_RETAIL_GENERALS : ARCHIVE_PRIORITY_RETAIL; + + if (TheFileSystem != nullptr && TheFileSystem->getExcludedPaths().matchesPath(path.str())) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Archive %s is excluded by user.exclude_paths", path.str()); + return ARCHIVE_PRIORITY_EXCLUDED; + } + + m_gameFolderArchives.push_back(path); + return generals ? ARCHIVE_PRIORITY_GAME_FOLDER_GENERALS : ARCHIVE_PRIORITY_GAME_FOLDER; +} + +Bool ArchiveFileSystem::parseMountEntry(const AsciiString& text, ArchiveMountEntry* entry, AsciiString* reason) +{ + auto fail = [reason](const char* why) + { + if (reason != nullptr) + *reason = why; + return FALSE; + }; + + std::string value = text.str(); + // trim + while (!value.empty() && isspace(static_cast(value.back()))) + value.pop_back(); + size_t start = 0; + while (start < value.size() && isspace(static_cast(value[start]))) + ++start; + value = value.substr(start); + if (value.empty()) + return fail("empty"); + + for (char& c : value) + { + if (c == '/') + c = '\\'; + if (static_cast(c) < 32 || c == '<' || c == '>' || c == '"' || c == '|') + return fail("invalid character"); + } + + ArchiveMountEntry::Base base; + std::string path; + std::string lowerPrefix = value.substr(0, 5); + for (char& c : lowerPrefix) + c = static_cast(tolower(static_cast(c))); + if (lowerPrefix == "game:") + { + base = ArchiveMountEntry::BASE_GAME; + path = value.substr(5); + } + else if (lowerPrefix == "user:") + { + base = ArchiveMountEntry::BASE_USER; + path = value.substr(5); + } + else if (value.size() > 3 && isalpha(static_cast(value[0])) && value[1] == ':' && value[2] == '\\') + { + base = ArchiveMountEntry::BASE_ABSOLUTE; + path = value; + } + else if (value.size() > 2 && value[0] == '\\' && value[1] == '\\') + { + base = ArchiveMountEntry::BASE_ABSOLUTE; + path = value; + } + else + { + return fail("needs the prefix game: or user:, or an absolute path"); + } + + if (base != ArchiveMountEntry::BASE_ABSOLUTE) + { + if (path.empty() || path[0] == '\\' || path.find(':') != std::string::npos) + return fail("invalid path"); + } + + // no parent folders + size_t pos = 0; + while (pos <= path.size()) + { + size_t next = path.find('\\', pos); + if (next == std::string::npos) + next = path.size(); + if (path.substr(pos, next - pos) == "..") + return fail("parent folder"); + pos = next + 1; + } + + const size_t lastSeparator = path.find_last_of('\\'); + const std::string lastComponent = lastSeparator == std::string::npos ? path : path.substr(lastSeparator + 1); + const std::string directory = lastSeparator == std::string::npos ? std::string() : path.substr(0, lastSeparator + 1); + if (directory.find_first_of("*?") != std::string::npos) + return fail("wildcards are only allowed in the file name"); + + std::string lowerLast = lastComponent; + for (char& c : lowerLast) + c = static_cast(tolower(static_cast(c))); + + ArchiveMountEntry::Kind kind; + if (lastComponent.empty()) + { + // a folder: every BIG in it + if (directory.empty() || directory == "\\\\") + return fail("invalid path"); + kind = ArchiveMountEntry::KIND_BIG_GLOB; + path += "*.big"; + } + else if (lowerLast.size() < 5 || lowerLast.compare(lowerLast.size() - 4, 4, ".big") != 0) + { + return fail("not a BIG file, a BIG folder or a *.big pattern"); + } + else if (lastComponent.find_first_of("*?") != std::string::npos) + { + kind = ArchiveMountEntry::KIND_BIG_GLOB; + } + else + { + kind = ArchiveMountEntry::KIND_BIG; + } + + if (entry != nullptr) + { + entry->m_kind = kind; + entry->m_base = base; + entry->m_path = path.c_str(); + } + return TRUE; +} + +void ArchiveFileSystem::mountListedArchive(const ArchiveMountEntry& entry, Int priority, const PathExcludeList* excludes) { -#if defined(GENERALS_ONLINE) && defined(GENERALS_ONLINE_COMMUNITY_PATCH_CHANGES) - // load community data patch BIG - if (NGMP_OnlineServicesManager::Settings.DataPacks_UseCommunityPatch() - && !TheGlobalData->m_commandLineData.isCommunityDataPatchDisabled()) - { - std::string strBigPath = std::format("{}GeneralsOnlineGameData/500_900_CommunityPatch_CoreINI.big", TheGlobalData->getPath_UserData().str()); - bool bLoaded = false; - if (TheLocalFileSystem->doesFileExist(strBigPath.c_str())) - { - ArchiveFile* archiveFile = openArchiveFile(strBigPath.c_str()); - if (archiveFile != nullptr) - { - // Sorted by filename so the patch respects the addon number order of BIGs in the game folder. - loadIntoDirectoryTree(archiveFile, FALSE, TRUE); - m_archiveFileMap[AsciiString(strBigPath.c_str())] = archiveFile; - bLoaded = true; - } - } - NetworkLog(ELogVerbosity::LOG_RELEASE, "Loaded community patch (%s): %d", strBigPath.c_str(), bLoaded); - } -#endif - - if (TheGlobalData->m_modBIG.isNotEmpty()) + std::string fullPath = entry.m_path.str(); + if (entry.m_base == ArchiveMountEntry::BASE_USER) + fullPath = std::string(TheGlobalData->getPath_UserData().str()) + fullPath; + + // the files to mount, in name order + std::vector bigPaths; + if (entry.m_kind == ArchiveMountEntry::KIND_BIG) { - ArchiveFile *archiveFile = openArchiveFile(TheGlobalData->m_modBIG.str()); + bigPaths.push_back(fullPath); + } + else + { + const size_t lastSeparator = fullPath.find_last_of('\\'); + const std::string directory = lastSeparator == std::string::npos ? std::string() : fullPath.substr(0, lastSeparator + 1); + const std::string mask = lastSeparator == std::string::npos ? fullPath : fullPath.substr(lastSeparator + 1); + FilenameList files; + TheLocalFileSystem->getFileListInDirectory(AsciiString(directory.c_str()), "", AsciiString(mask.c_str()), files, FALSE); + for (FilenameListIter it = files.begin(); it != files.end(); ++it) + bigPaths.push_back(it->str()); + } - if (archiveFile != nullptr) { - DEBUG_LOG(("ArchiveFileSystem::loadMods - loading %s into the directory tree.", TheGlobalData->m_modBIG.str())); - loadIntoDirectoryTree(archiveFile, TRUE); - m_archiveFileMap[TheGlobalData->m_modBIG] = archiveFile; - DEBUG_LOG(("ArchiveFileSystem::loadMods - %s inserted into the archive file map.", TheGlobalData->m_modBIG.str())); + Int mounted = 0; + for (const std::string& bigPath : bigPaths) + { + if (!TheLocalFileSystem->doesFileExist(bigPath.c_str())) + continue; + if (excludes != nullptr && !excludes->isEmpty() && excludes->matches(PathExcludeList::makeKey(bigPath.c_str(), TheGlobalData->getPath_UserData().str()))) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Archive %s is excluded", bigPath.c_str()); + continue; } - else + if (m_archiveFileMap.find(AsciiString(bigPath.c_str())) != m_archiveFileMap.end()) { - DEBUG_LOG(("ArchiveFileSystem::loadMods - could not openArchiveFile(%s)", TheGlobalData->m_modBIG.str())); + NetworkLog(ELogVerbosity::LOG_RELEASE, "Archive %s is already mounted", bigPath.c_str()); + continue; } + ArchiveFile* archiveFile = openArchiveFile(bigPath.c_str()); + if (archiveFile == nullptr) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Archive %s could not be opened", bigPath.c_str()); + continue; + } + loadIntoDirectoryTree(archiveFile, priority); + m_archiveFileMap[AsciiString(bigPath.c_str())] = archiveFile; + ++mounted; + } + NetworkLog(ELogVerbosity::LOG_RELEASE, "Archive %s at priority %d: %d archives", fullPath.c_str(), priority, mounted); +} + +void ArchiveFileSystem::mountDataPackLayer(const char* layer, Bool enabled, const std::vector& includePaths, const PathExcludeList* excludes, Int priority) +{ + if (!enabled) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Data pack layer %s is disabled", layer); + return; } - if (TheGlobalData->m_modDir.isNotEmpty()) + NetworkLog(ELogVerbosity::LOG_RELEASE, "Data pack layer %s: %d include_paths", layer, static_cast(includePaths.size())); + for (size_t i = 0; i < includePaths.size(); ++i) { - MAYBE_UNUSED Bool ret = loadBigFilesFromDirectory(TheGlobalData->m_modDir, "*.big", TRUE); - (void)ret; - DEBUG_ASSERTLOG(ret, ("loadBigFilesFromDirectory(%s) returned FALSE!", TheGlobalData->m_modDir.str())); + ArchiveMountEntry entry; + AsciiString reason; + if (!parseMountEntry(AsciiString(includePaths[i].c_str()), &entry, &reason)) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Data pack layer %s: path ignored (%s): %s", layer, reason.str(), includePaths[i].c_str()); + continue; + } + mountListedArchive(entry, priority + static_cast(i), excludes); } } @@ -306,15 +504,15 @@ ArchiveFileSystem::ArchivedDirectoryInfoResult ArchiveFileSystem::getArchivedDir AsciiString token; AsciiString tokenizer = directory; tokenizer.toLower(); - Bool infoInPath = tokenizer.nextToken(&token, "\\/"); + tokenizer.nextToken(&token, "\\/"); - while (infoInPath && (!token.find('.') || tokenizer.find('.'))) + while (!tokenizer.isEmpty()) { ArchivedDirectoryInfoMap::iterator tempiter = dirInfo->m_directories.find(token); if (tempiter != dirInfo->m_directories.end()) { dirInfo = &tempiter->second; - infoInPath = tokenizer.nextToken(&token, "\\/"); + tokenizer.nextToken(&token, "\\/"); } else { diff --git a/Core/GameEngine/Source/Common/System/AsciiString.cpp b/Core/GameEngine/Source/Common/System/AsciiString.cpp index afa1b96186b..1cb3bfb6046 100644 --- a/Core/GameEngine/Source/Common/System/AsciiString.cpp +++ b/Core/GameEngine/Source/Common/System/AsciiString.cpp @@ -607,16 +607,21 @@ Bool AsciiString::isNone() const } //----------------------------------------------------------------------------- -Bool AsciiString::nextToken(AsciiString* tok, const char* seps) +Bool AsciiString::nextToken(AsciiString* tok, const char* separators) { - if (this->isEmpty() || tok == this) + DEBUG_ASSERTCRASH(tok != this, ("Tokenizer and Token cannot be the same object")); + + if (this->isEmpty()) + { + tok->clear(); return false; + } - if (seps == nullptr) - seps = " \n\r\t"; + if (separators == nullptr) + separators = " \n\r\t"; - char* start = skipSeps(peek(), seps); - char* end = skipNonSeps(start, seps); + char* start = skipSeps(peek(), separators); + char* end = skipNonSeps(start, separators); if (end > start) { diff --git a/Core/GameEngine/Source/Common/System/BIGFileSystem.cpp b/Core/GameEngine/Source/Common/System/BIGFileSystem.cpp new file mode 100644 index 00000000000..7b84df515a0 --- /dev/null +++ b/Core/GameEngine/Source/Common/System/BIGFileSystem.cpp @@ -0,0 +1,332 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//////// BIGFileSystem.cpp /////////////////////////// +// Bryan Cleveland, August 2002 +///////////////////////////////////////////////////////////// + +#include "PreRTS.h" + +#include "Common/BIGFileSystem.h" + +#include "Common/AudioAffect.h" +#include "Common/ArchiveFile.h" +#include "Common/file.h" +#include "Common/GameAudio.h" +#include "Common/GameMemory.h" +#include "Common/LocalFileSystem.h" + +#if RTS_ZEROHOUR +#include "Common/Registry.h" +#endif + +#include "Utility/endian_compat.h" +#include "../NGMP_include.h" +#include "../OnlineServices_Init.h" + + +static const char BIGFileIdentifier[] = { 'B', 'I', 'G', 'F' }; + +struct BIGFileHeader { + char identifier[sizeof(BIGFileIdentifier)]; + UnsignedInt archiveFileSize; // little endian, retail reads it as Int so 2 GB max + UnsignedInt numFiles; // big endian + UnsignedInt firstEntryOffset; // big endian +}; + +struct BIGFileEntryHeader { + UnsignedInt fileOffset; // big endian + UnsignedInt fileSize; // big endian +}; + +static_assert(sizeof(BIGFileHeader) == 16, "Incorrect BIG header size"); +static_assert(sizeof(BIGFileEntryHeader) == 8, "Incorrect BIG entry header size"); + +// Returns the length of the string, or -1 if it is truncated or does not fit the buffer. +static Int readNullTerminatedString(File *fp, char *buffer, Int bufferSize) { + for (Int i = 0; i < bufferSize; ++i) { + if (fp->read(buffer + i, 1) != 1) { + return -1; + } + if (buffer[i] == 0) { + return i; + } + } + return -1; +} + +BIGFileSystem::BIGFileSystem() : ArchiveFileSystem() { +} + +BIGFileSystem::~BIGFileSystem() { +} + +void BIGFileSystem::init() { + DEBUG_ASSERTCRASH(TheLocalFileSystem != nullptr, ("TheLocalFileSystem must be initialized before TheArchiveFileSystem.")); + if (TheLocalFileSystem == nullptr) { + return; + } + + loadArchiveFilesFromDirectory("", "*.big"); + +#if RTS_ZEROHOUR + // load original Generals assets + // The Steam install ships them in ZH_Generals, which the scan above already mounted. Mounting the Generals install as well would only add its mods. + if (TheLocalFileSystem->doesFileExist("ZH_Generals\\INI.big")) { + m_generalsBaseSource = "ZH_Generals folder"; + return; + } + + AsciiString installPath; + GetStringFromGeneralsRegistry("", "InstallPath", installPath ); + //@todo this will need to be ramped up to a crash for release + DEBUG_ASSERTCRASH(!installPath.isEmpty(), ("Be 1337! Go install Generals!")); + if (!installPath.isEmpty()) { + m_generalsBaseSource.format("Generals install folder %s", installPath.str()); + loadArchiveFilesFromDirectory(installPath, "*.big"); + } +#endif +} + +void BIGFileSystem::reset() { +} + +void BIGFileSystem::update() { +} + +void BIGFileSystem::postProcessLoad() { +} + +ArchiveFile * BIGFileSystem::openArchiveFile(const Char *filename) { + File *fp = TheLocalFileSystem->openFile(filename, File::READ | File::BINARY); + AsciiString archiveFileName; + archiveFileName = filename; + archiveFileName.toLower(); + UnsignedInt archiveFileSize = 0; + UnsignedInt numLittleFiles = 0; + BIGFileHeader header; + + DEBUG_LOG(("BIGFileSystem::openArchiveFile - opening BIG file %s", filename)); + + if (fp == nullptr) { + DEBUG_CRASH(("Could not open archive file %s for parsing", filename)); + return nullptr; + } + + AsciiString asciibuf; + char buffer[_MAX_PATH]; + if (fp->read(&header, sizeof(header)) != sizeof(header) || memcmp(header.identifier, BIGFileIdentifier, sizeof(BIGFileIdentifier)) != 0) { + DEBUG_CRASH(("Error reading BIG file header in file %s", filename)); + fp->close(); + fp = nullptr; + return nullptr; + } + + archiveFileSize = letoh(header.archiveFileSize); + numLittleFiles = betoh(header.numFiles); + + DEBUG_LOG(("BIGFileSystem::openArchiveFile - size of archive file is %u bytes", archiveFileSize)); + +// char t; + + DEBUG_LOG(("BIGFileSystem::openArchiveFile - %u are contained in archive", numLittleFiles)); +// for (Int i = 0; i < 2; ++i) { +// t = buffer[i]; +// buffer[i] = buffer[(4-i)-1]; +// buffer[(4-i)-1] = t; +// } + + // read in each directory listing. + ArchivedFileInfo fileInfo; + ArchiveFile *archiveFile = NEW ArchiveFile(filename, AsciiString::TheEmptyString); + + Bool corrupt = FALSE; + for (UnsignedInt i = 0; i < numLittleFiles; ++i) { + BIGFileEntryHeader entry; + if (fp->read(&entry, sizeof(entry)) != sizeof(entry)) { + corrupt = TRUE; + break; + } + + fileInfo.m_archiveFilename = archiveFileName; + fileInfo.m_offset = betoh(entry.fileOffset); + fileInfo.m_size = betoh(entry.fileSize); + + // read in the path name of the file. + const Int pathIndex = readNullTerminatedString(fp, buffer, sizeof(buffer)); + if (pathIndex < 0) { + corrupt = TRUE; + break; + } + + Int filenameIndex = pathIndex; + while ((filenameIndex >= 0) && (buffer[filenameIndex] != '\\') && (buffer[filenameIndex] != '/')) { + --filenameIndex; + } + + fileInfo.m_filename = (char *)(buffer + filenameIndex + 1); + fileInfo.m_filename.toLower(); + buffer[filenameIndex + 1] = 0; + + AsciiString path; + path = buffer; + + AsciiString debugpath; + debugpath = path; + debugpath.concat(fileInfo.m_filename); +// DEBUG_LOG(("BIGFileSystem::openArchiveFile - adding file %s to archive file %s, file number %d", debugpath.str(), fileInfo->m_archiveFilename.str(), i)); + + archiveFile->addFile(path, &fileInfo); + } + + if (corrupt) { + DEBUG_CRASH(("Truncated or corrupt directory in BIG file %s", filename)); + delete archiveFile; + fp->close(); + fp = nullptr; + return nullptr; + } + + archiveFile->attachFile(fp); + + // leave fp open as the archive file will be using it. + + return archiveFile; +} + +void BIGFileSystem::closeArchiveFile(const Char *filename) { + // Need to close the specified big file + ArchiveFileMap::iterator it = m_archiveFileMap.find(filename); + if (it == m_archiveFileMap.end()) { + return; + } + + if (stricmp(filename, MUSIC_BIG) == 0) { + // Stop the current audio + TheAudio->stopAudio(AudioAffect_Music); + + // No need to turn off other audio, as the lookups will just fail. + } + DEBUG_ASSERTCRASH(stricmp(filename, MUSIC_BIG) == 0, ("Attempting to close Archive file '%s', need to add code to handle its shutdown correctly.", filename)); + + // may need to do some other processing here first. + + delete (it->second); + m_archiveFileMap.erase(it); +} + +void BIGFileSystem::closeAllArchiveFiles() { +} + +void BIGFileSystem::closeAllFiles() { +} + +Bool BIGFileSystem::loadArchiveFilesFromDirectory(AsciiString dir, AsciiString fileMask, Bool overwrite) { + + FilenameList filenameList; + TheLocalFileSystem->getFileListInDirectory(dir, "", fileMask, filenameList, TRUE); + + Bool actuallyAdded = FALSE; + FilenameListIter it = filenameList.begin(); + while (it != filenameList.end()) { +#if RTS_ZEROHOUR + // TheSuperHackers @bugfix bobtista 18/11/2025 Skip duplicate INIZH.big in Data\INI to prevent CRC mismatches. + // English, Chinese, and Korean SKUs shipped with two INIZH.big files (one in Run directory, one in Run\Data\INI). + // The DeleteFile cleanup doesn't work on EA App/Origin installs because the folder is not writable, so we skip loading it instead. + if (it->endsWithNoCase("Data\\INI\\INIZH.big") || it->endsWithNoCase("Data/INI/INIZH.big")) { + it++; + continue; + } +#endif + +#if RTS_ZEROHOUR + const Bool zeroHour = TRUE; +#else + const Bool zeroHour = FALSE; +#endif + // The first scan is the game folder, the second one the Generals install: the retail archives are the bottom of the lookup + const Int priority = overwrite ? getNextModPriority() : getGameFolderArchivePriority((*it), dir.isNotEmpty(), zeroHour); + if (priority == ARCHIVE_PRIORITY_EXCLUDED) { + it++; + continue; + } + + ArchiveFile *archiveFile = openArchiveFile((*it).str()); + + if (archiveFile != nullptr) { + DEBUG_LOG(("BIGFileSystem::loadArchiveFilesFromDirectory - loading %s into the directory tree.", (*it).str())); + loadIntoDirectoryTree(archiveFile, priority); + m_archiveFileMap[(*it)] = archiveFile; + DEBUG_LOG(("BIGFileSystem::loadArchiveFilesFromDirectory - %s inserted into the archive file map.", (*it).str())); + actuallyAdded = TRUE; + } + + it++; + } + + return actuallyAdded; +} + +void BIGFileSystem::loadMods() +{ +#if defined(GENERALS_ONLINE) + // Order, highest first: loose files of the game folder, -mod and -modDir, the BIGs of the game folders that are not retail (user.exclude_paths removes some), + // the data pack layers of settings.json (user, generals_online, community_patch), the retail archives. The BIGs of the game folders were mounted by init(). + NetworkLog(ELogVerbosity::LOG_RELEASE, "Generals data: %s", m_generalsBaseSource.isEmpty() ? "none" : m_generalsBaseSource.str()); + NetworkLog(ELogVerbosity::LOG_RELEASE, "Game folder archives above the data pack layers: %d", static_cast(m_gameFolderArchives.size())); + for (const AsciiString& gameFolderArchive : m_gameFolderArchives) + NetworkLog(ELogVerbosity::LOG_RELEASE, " %s", gameFolderArchive.str()); + + const GenOnlineSettings& settings = NGMP_OnlineServicesManager::Settings; + mountDataPackLayer("user", settings.DataPacks_User().m_enabled, settings.DataPacks_User().m_includePaths, &TheFileSystem->getExcludedPaths(), ARCHIVE_PRIORITY_USER); + mountDataPackLayer("generals_online", settings.DataPacks_GeneralsOnline().m_enabled, settings.DataPacks_GeneralsOnline().m_includePaths, nullptr, ARCHIVE_PRIORITY_GENERALS_ONLINE); + + // -disableCommunityDataPatch skips the community_patch layer for this launch only, the setting is not changed + const Bool communityPatchEnabled = settings.DataPacks_CommunityPatch().m_enabled && !TheGlobalData->m_commandLineData.isCommunityDataPatchDisabled(); + mountDataPackLayer("community_patch", communityPatchEnabled, settings.DataPacks_CommunityPatch().m_includePaths, nullptr, ARCHIVE_PRIORITY_COMMUNITY_PATCH); +#endif + + if (TheGlobalData->m_modBIG.isNotEmpty()) + { + ArchiveFile *archiveFile = openArchiveFile(TheGlobalData->m_modBIG.str()); + + if (archiveFile != nullptr) { + DEBUG_LOG(("BIGFileSystem::loadMods - loading %s into the directory tree.", TheGlobalData->m_modBIG.str())); + loadIntoDirectoryTree(archiveFile, getNextModPriority()); + m_archiveFileMap[TheGlobalData->m_modBIG] = archiveFile; + DEBUG_LOG(("BIGFileSystem::loadMods - %s inserted into the archive file map.", TheGlobalData->m_modBIG.str())); + } + else + { + DEBUG_LOG(("BIGFileSystem::loadMods - could not openArchiveFile(%s)", TheGlobalData->m_modBIG.str())); + } + } + + if (TheGlobalData->m_modDir.isNotEmpty()) + { + MAYBE_UNUSED Bool ret = loadArchiveFilesFromDirectory(TheGlobalData->m_modDir, "*.big", TRUE); + (void)ret; + DEBUG_ASSERTLOG(ret, ("loadArchiveFilesFromDirectory(%s) returned FALSE!", TheGlobalData->m_modDir.str())); + } +} diff --git a/Core/GameEngine/Source/Common/System/FileSystem.cpp b/Core/GameEngine/Source/Common/System/FileSystem.cpp index b8e4c4695b6..e4df60933a4 100644 --- a/Core/GameEngine/Source/Common/System/FileSystem.cpp +++ b/Core/GameEngine/Source/Common/System/FileSystem.cpp @@ -56,6 +56,8 @@ #include "Lib/PathUtil.h" +#include + DECLARE_PERF_TIMER(FileSystem) @@ -118,6 +120,184 @@ FileSystem *TheFileSystem = nullptr; //---------------------------------------------------------------------------- +//============================================================================ +// PathExcludeList +//============================================================================ + +static std::string normalizeExcludeText(const std::string& text) +{ + std::string value = text; + for (size_t i = 0; i < value.size(); ++i) + { + if (value[i] == '/') + value[i] = '\\'; + value[i] = static_cast(tolower(static_cast(value[i]))); + } + return value; +} + +static Bool isAbsoluteExcludePath(const std::string& value) +{ + return (value.size() > 2 && isalpha(static_cast(value[0])) && value[1] == ':' && value[2] == '\\') + || (value.size() > 2 && value[0] == '\\' && value[1] == '\\'); +} + +static std::string stripCurrentFolder(const std::string& value) +{ + size_t start = 0; + while (start + 2 <= value.size() && value.compare(start, 2, ".\\") == 0) + start += 2; + return value.substr(start); +} + +Bool PathExcludeList::parse(const std::string& text, std::string* pattern, AsciiString* reason) +{ + std::string value = text; + while (!value.empty() && isspace(static_cast(value[value.size() - 1]))) + value.erase(value.size() - 1); + size_t start = 0; + while (start < value.size() && isspace(static_cast(value[start]))) + ++start; + value = normalizeExcludeText(value.substr(start)); + + if (value.empty()) + { + if (reason != nullptr) + *reason = "empty"; + return FALSE; + } + for (size_t i = 0; i < value.size(); ++i) + { + const unsigned char c = static_cast(value[i]); + if (c < 32 || c == '<' || c == '>' || c == '"' || c == '|') + { + if (reason != nullptr) + *reason = "invalid character"; + return FALSE; + } + } + + std::string rest; + std::string prefix; + if (value.compare(0, 5, "game:") == 0 || value.compare(0, 5, "user:") == 0) + { + prefix = value.substr(0, 5); + rest = stripCurrentFolder(value.substr(5)); + if (rest.empty() || rest[0] == '\\' || rest.find(':') != std::string::npos) + { + if (reason != nullptr) + *reason = "invalid path"; + return FALSE; + } + } + else if (isAbsoluteExcludePath(value)) + { + rest = value; + } + else + { + if (reason != nullptr) + *reason = "needs the prefix game: or user:, or an absolute path"; + return FALSE; + } + + // no parent folders + size_t pos = 0; + while (pos <= rest.size()) + { + size_t next = rest.find('\\', pos); + if (next == std::string::npos) + next = rest.size(); + if (next - pos == 2 && rest.compare(pos, 2, "..") == 0) + { + if (reason != nullptr) + *reason = "parent folder"; + return FALSE; + } + pos = next + 1; + } + + // a folder: everything in it + if (rest[rest.size() - 1] == '\\') + rest += "*"; + + if (pattern != nullptr) + *pattern = prefix + rest; + return TRUE; +} + +std::string PathExcludeList::makeKey(const Char* path) +{ + std::string value = stripCurrentFolder(normalizeExcludeText(path)); + if (isAbsoluteExcludePath(value)) + return value; + return "game:" + value; +} + +std::string PathExcludeList::makeKey(const Char* path, const Char* userDataPath) +{ + std::string value = normalizeExcludeText(path); + std::string userData = normalizeExcludeText(userDataPath); + if (!userData.empty() && userData[userData.size() - 1] != '\\') + userData += "\\"; + if (!userData.empty() && value.size() > userData.size() && value.compare(0, userData.size(), userData) == 0) + return "user:" + value.substr(userData.size()); + return makeKey(path); +} + +void PathExcludeList::set(const std::vector& entries) +{ + m_patterns.clear(); + for (size_t i = 0; i < entries.size(); ++i) + { + std::string pattern; + if (parse(entries[i], &pattern, nullptr)) + m_patterns.push_back(pattern); + } +} + +// * matches any text, ? one character +static Bool matchesExcludePattern(const char* pattern, const char* text) +{ + const char* starPattern = nullptr; + const char* starText = nullptr; + while (*text != '\0') + { + if (*pattern == '*') + { + starPattern = ++pattern; + starText = text; + } + else if (*pattern == '?' || *pattern == *text) + { + ++pattern; + ++text; + } + else if (starPattern != nullptr) + { + pattern = starPattern; + text = ++starText; + } + else + { + return FALSE; + } + } + while (*pattern == '*') + ++pattern; + return *pattern == '\0'; +} + +Bool PathExcludeList::matches(const std::string& key) const +{ + for (size_t i = 0; i < m_patterns.size(); ++i) + { + if (matchesExcludePattern(m_patterns[i].c_str(), key.c_str())) + return TRUE; + } + return FALSE; +} + //============================================================================ // FileSystem::FileSystem //============================================================================ @@ -179,14 +359,17 @@ File* FileSystem::openFile( const Char *filename, Int access, size_t bufferSize if ( TheLocalFileSystem != nullptr ) { + // an excluded loose file does not exist, unless the file is written + const Bool excluded = (access & (File::WRITE | File::APPEND | File::CREATE | File::TRUNCATE)) == 0 && isLooseFileExcluded(filename); + if (instance != 0) { - if (TheLocalFileSystem->doesFileExist(filename)) + if (!excluded && TheLocalFileSystem->doesFileExist(filename)) { --instance; } } - else + else if (!excluded) { file = TheLocalFileSystem->openFile( filename, access, bufferSize ); @@ -242,7 +425,7 @@ Bool FileSystem::doesFileExist(const Char *filename, FileInstance instance) cons } #endif - if (TheLocalFileSystem->doesFileExist(filename)) + if (!isLooseFileExcluded(filename) && TheLocalFileSystem->doesFileExist(filename)) { if (instance == 0) { @@ -286,7 +469,20 @@ Bool FileSystem::doesFileExist(const Char *filename, FileInstance instance) cons void FileSystem::getFileListInDirectory(const AsciiString& directory, const AsciiString& searchName, FilenameList &filenameList, Bool searchSubdirectories) const { USE_PERF_TIMER(FileSystem) - TheLocalFileSystem->getFileListInDirectory(AsciiString::TheEmptyString, directory, searchName, filenameList, searchSubdirectories); + if (m_excludedPaths.isEmpty()) + { + TheLocalFileSystem->getFileListInDirectory(AsciiString::TheEmptyString, directory, searchName, filenameList, searchSubdirectories); + } + else + { + FilenameList localFiles; + TheLocalFileSystem->getFileListInDirectory(AsciiString::TheEmptyString, directory, searchName, localFiles, searchSubdirectories); + for (FilenameListIter it = localFiles.begin(); it != localFiles.end(); ++it) + { + if (!isLooseFileExcluded(it->str())) + filenameList.insert(*it); + } + } TheArchiveFileSystem->getFileListInDirectory(AsciiString::TheEmptyString, directory, searchName, filenameList, searchSubdirectories); } @@ -304,7 +500,7 @@ Bool FileSystem::getFileInfo(const AsciiString& filename, FileInfo *fileInfo, Fi } memset(fileInfo, 0, sizeof(*fileInfo)); - if (TheLocalFileSystem->getFileInfo(filename, fileInfo)) { + if (!isLooseFileExcluded(filename.str()) && TheLocalFileSystem->getFileInfo(filename, fileInfo)) { if (instance == 0) { return TRUE; } diff --git a/Core/GameEngine/Source/Common/System/UnicodeString.cpp b/Core/GameEngine/Source/Common/System/UnicodeString.cpp index e21c0ea38dc..3e8980b6395 100644 --- a/Core/GameEngine/Source/Common/System/UnicodeString.cpp +++ b/Core/GameEngine/Source/Common/System/UnicodeString.cpp @@ -460,20 +460,25 @@ Bool UnicodeString::endsWithNoCase(const WideChar* p) const } //----------------------------------------------------------------------------- -Bool UnicodeString::nextToken(UnicodeString* tok, UnicodeString delimiters) +Bool UnicodeString::nextToken(UnicodeString* tok, const WideChar* separators) { - if (this->isEmpty() || tok == this) + DEBUG_ASSERTCRASH(tok != this, ("Tokenizer and Token cannot be the same object")); + + if (this->isEmpty()) + { + tok->clear(); return false; + } - if (delimiters.isEmpty()) - delimiters = L" \t\n\r"; + if (separators == nullptr) + separators = L" \t\n\r"; Int offset; - offset = wcsspn(peek(), delimiters.str()); + offset = wcsspn(peek(), separators); WideChar* start = peek() + offset; - offset = wcscspn(start, delimiters.str()); + offset = wcscspn(start, separators); WideChar* end = start + offset; if (end > start) diff --git a/Core/GameEngineDevice/CMakeLists.txt b/Core/GameEngineDevice/CMakeLists.txt index 6added5403b..71933e7c69f 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -83,8 +83,6 @@ set(GAMEENGINEDEVICE_SRC # Include/W3DDevice/GameLogic/W3DGameLogic.h # Include/W3DDevice/GameLogic/W3DGhostObject.h # Include/W3DDevice/GameLogic/W3DTerrainLogic.h - Include/Win32Device/Common/Win32BIGFile.h - Include/Win32Device/Common/Win32BIGFileSystem.h # Include/Win32Device/Common/Win32GameEngine.h Include/Win32Device/Common/Win32LocalFile.h Include/Win32Device/Common/Win32LocalFileSystem.h @@ -186,8 +184,6 @@ set(GAMEENGINEDEVICE_SRC # Source/W3DDevice/GameLogic/W3DGameLogic.cpp # Source/W3DDevice/GameLogic/W3DGhostObject.cpp # Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp - Source/Win32Device/Common/Win32BIGFile.cpp - Source/Win32Device/Common/Win32BIGFileSystem.cpp # Source/Win32Device/Common/Win32GameEngine.cpp Source/Win32Device/Common/Win32LocalFile.cpp Source/Win32Device/Common/Win32LocalFileSystem.cpp @@ -200,12 +196,8 @@ set(GAMEENGINEDEVICE_SRC # Add C++ 17 FileSystem implementation for non-VS6 builds if(NOT IS_VS6_BUILD) list(APPEND GAMEENGINEDEVICE_SRC - Include/StdDevice/Common/StdBIGFile.h - Include/StdDevice/Common/StdBIGFileSystem.h Include/StdDevice/Common/StdLocalFile.h Include/StdDevice/Common/StdLocalFileSystem.h - Source/StdDevice/Common/StdBIGFile.cpp - Source/StdDevice/Common/StdBIGFileSystem.cpp Source/StdDevice/Common/StdLocalFile.cpp Source/StdDevice/Common/StdLocalFileSystem.cpp ) diff --git a/Core/GameEngineDevice/Include/StdDevice/Common/StdBIGFile.h b/Core/GameEngineDevice/Include/StdDevice/Common/StdBIGFile.h deleted file mode 100644 index 36538853352..00000000000 --- a/Core/GameEngineDevice/Include/StdDevice/Common/StdBIGFile.h +++ /dev/null @@ -1,53 +0,0 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -/////// StdBIGFile.h //////////////////////////////////// -// Stephan Vedder, April 2025 -/////////////////////////////////////////////////////////// - -#pragma once - -#include "Common/ArchiveFile.h" -#include "Common/AsciiString.h" -#include "Common/List.h" - -class StdBIGFile : public ArchiveFile -{ - public: - StdBIGFile(AsciiString name, AsciiString path); - virtual ~StdBIGFile() override; - - virtual Bool getFileInfo(const AsciiString& filename, FileInfo *fileInfo) const override; ///< fill in the fileInfo struct with info about the requested file. - virtual File* openFile( const Char *filename, Int access = 0 ) override;///< Open the specified file within the BIG file - virtual void closeAllFiles() override; ///< Close all file opened in this BIG file - virtual AsciiString getName() override; ///< Returns the name of the BIG file - virtual AsciiString getPath() override; ///< Returns full path and name of BIG file - virtual void setSearchPriority( Int new_priority ) override; ///< Set this BIG file's search priority - virtual void close() override; ///< Close this BIG file - - protected: - - AsciiString m_name; ///< BIG file name - AsciiString m_path; ///< BIG file path -}; diff --git a/Core/GameEngineDevice/Include/StdDevice/Common/StdBIGFileSystem.h b/Core/GameEngineDevice/Include/StdDevice/Common/StdBIGFileSystem.h deleted file mode 100644 index b8cb316ab7c..00000000000 --- a/Core/GameEngineDevice/Include/StdDevice/Common/StdBIGFileSystem.h +++ /dev/null @@ -1,55 +0,0 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -//////// StdBIGFileSystem.h /////////////////////////// -// Stephan Vedder, April 2025 -///////////////////////////////////////////////////////////// - -#pragma once - -#include "Common/ArchiveFileSystem.h" - -class StdBIGFileSystem : public ArchiveFileSystem -{ -public: - StdBIGFileSystem(); - virtual ~StdBIGFileSystem() override; - - virtual void init() override; - virtual void update() override; - virtual void reset() override; - virtual void postProcessLoad() override; - - // ArchiveFile operations - virtual void closeAllArchiveFiles() override; ///< Close all Archivefiles currently open - - // File operations - virtual ArchiveFile * openArchiveFile(const Char *filename) override; - virtual void closeArchiveFile(const Char *filename) override; - virtual void closeAllFiles() override; ///< Close all files associated with ArchiveFiles - - virtual Bool loadBigFilesFromDirectory(AsciiString dir, AsciiString fileMask, Bool overwrite = FALSE) override; -protected: - -}; diff --git a/Core/GameEngineDevice/Include/Win32Device/Common/Win32BIGFile.h b/Core/GameEngineDevice/Include/Win32Device/Common/Win32BIGFile.h deleted file mode 100644 index 093aadbcf4c..00000000000 --- a/Core/GameEngineDevice/Include/Win32Device/Common/Win32BIGFile.h +++ /dev/null @@ -1,53 +0,0 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -/////// Win32BIGFile.h //////////////////////////////////// -// Bryan Cleveland, August 2002 -/////////////////////////////////////////////////////////// - -#pragma once - -#include "Common/ArchiveFile.h" -#include "Common/AsciiString.h" -#include "Common/List.h" - -class Win32BIGFile : public ArchiveFile -{ - public: - Win32BIGFile(AsciiString name, AsciiString path); - virtual ~Win32BIGFile() override; - - virtual Bool getFileInfo(const AsciiString& filename, FileInfo *fileInfo) const override; ///< fill in the fileInfo struct with info about the requested file. - virtual File* openFile( const Char *filename, Int access = 0 ) override;///< Open the specified file within the BIG file - virtual void closeAllFiles() override; ///< Close all file opened in this BIG file - virtual AsciiString getName() override; ///< Returns the name of the BIG file - virtual AsciiString getPath() override; ///< Returns full path and name of BIG file - virtual void setSearchPriority( Int new_priority ) override; ///< Set this BIG file's search priority - virtual void close() override; ///< Close this BIG file - - protected: - - AsciiString m_name; ///< BIG file name - AsciiString m_path; ///< BIG file path -}; diff --git a/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFile.cpp b/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFile.cpp deleted file mode 100644 index c873b733524..00000000000 --- a/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFile.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -////// StdBIGFile.cpp ///////////////////////// -// Stephan Vedder, April 2025 -///////////////////////////////////////////////////// - -#include "Common/LocalFile.h" -#include "Common/LocalFileSystem.h" -#include "Common/RAMFile.h" -#include "Common/StreamingArchiveFile.h" -#include "Common/GameMemory.h" -#include "Common/PerfTimer.h" -#include "StdDevice/Common/StdBIGFile.h" - -//============================================================================ -// StdBIGFile::StdBIGFile -//============================================================================ - -StdBIGFile::StdBIGFile(AsciiString name, AsciiString path) - : m_name(name) - , m_path(path) -{ - -} - -//============================================================================ -// StdBIGFile::~StdBIGFile -//============================================================================ - -StdBIGFile::~StdBIGFile() -{ - -} - -//============================================================================ -// StdBIGFile::openFile -//============================================================================ - -File* StdBIGFile::openFile( const Char *filename, Int access ) -{ - const ArchivedFileInfo *fileInfo = getArchivedFileInfo(AsciiString(filename)); - - if (fileInfo == nullptr) { - return nullptr; - } - - RAMFile *ramFile = nullptr; - - if (BitIsSet(access, File::STREAMING)) - ramFile = newInstance( StreamingArchiveFile ); - else - ramFile = newInstance( RAMFile ); - - ramFile->deleteOnClose(); - if (ramFile->openFromArchive(m_file, fileInfo->m_filename, fileInfo->m_offset, fileInfo->m_size) == FALSE) { - ramFile->close(); - ramFile = nullptr; - return nullptr; - } - - if ((access & File::WRITE) == 0) { - // requesting read only access. Just return the RAM file. - return ramFile; - } - - // whoever is opening this file wants write access, so copy the file to the local disk - // and return that file pointer. - - constexpr size_t bufferSize = 0; - File *localFile = TheLocalFileSystem->openFile(filename, access, bufferSize); - if (localFile != nullptr) { - ramFile->copyDataToFile(localFile); - } - - ramFile->close(); - ramFile = nullptr; - - return localFile; -} - -//============================================================================ -// StdBIGFile::closeAllFiles -//============================================================================ - -void StdBIGFile::closeAllFiles() -{ - -} - -//============================================================================ -// StdBIGFile::getName -//============================================================================ - -AsciiString StdBIGFile::getName() -{ - return m_name; -} - -//============================================================================ -// StdBIGFile::getPath -//============================================================================ - -AsciiString StdBIGFile::getPath() -{ - return m_path; -} - -//============================================================================ -// StdBIGFile::setSearchPriority -//============================================================================ - -void StdBIGFile::setSearchPriority( Int new_priority ) -{ - -} - -//============================================================================ -// StdBIGFile::close -//============================================================================ - -void StdBIGFile::close() -{ - -} - -//============================================================================ -// StdBIGFile::getFileInfo -//============================================================================ - -Bool StdBIGFile::getFileInfo(const AsciiString& filename, FileInfo *fileInfo) const -{ - const ArchivedFileInfo *tempFileInfo = getArchivedFileInfo(filename); - - if (tempFileInfo == nullptr) { - return FALSE; - } - - TheLocalFileSystem->getFileInfo(AsciiString(m_file->getName()), fileInfo); - - // fill in the size info. Since the size can't be bigger than a JUNK file, the high Int will always be 0. - fileInfo->sizeHigh = 0; - fileInfo->sizeLow = tempFileInfo->m_size; - - return TRUE; -} - diff --git a/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFileSystem.cpp b/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFileSystem.cpp deleted file mode 100644 index ffd0150f4c5..00000000000 --- a/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFileSystem.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -//////// StdBIGFileSystem.h /////////////////////////// -// Stephan Vedder, April 2025 -///////////////////////////////////////////////////////////// - -#include "Common/AudioAffect.h" -#include "Common/ArchiveFile.h" -#include "Common/ArchiveFileSystem.h" -#include "Common/file.h" -#include "Common/GameAudio.h" -#include "Common/GameMemory.h" -#include "Common/LocalFileSystem.h" - -#if RTS_ZEROHOUR -#include "Common/Registry.h" -#endif - -#include "StdDevice/Common/StdBIGFile.h" -#include "StdDevice/Common/StdBIGFileSystem.h" -#include "Utility/endian_compat.h" - -static const char *BIGFileIdentifier = "BIGF"; - -StdBIGFileSystem::StdBIGFileSystem() : ArchiveFileSystem() { -} - -StdBIGFileSystem::~StdBIGFileSystem() { -} - -void StdBIGFileSystem::init() { - DEBUG_ASSERTCRASH(TheLocalFileSystem != nullptr, ("TheLocalFileSystem must be initialized before TheArchiveFileSystem.")); - if (TheLocalFileSystem == nullptr) { - return; - } - - loadBigFilesFromDirectory("", "*.big"); - -#if RTS_ZEROHOUR - // load original Generals assets - AsciiString installPath; - GetStringFromGeneralsRegistry("", "InstallPath", installPath ); - //@todo this will need to be ramped up to a crash for release - DEBUG_ASSERTCRASH(!installPath.isEmpty(), ("Be 1337! Go install Generals!")); - if (!installPath.isEmpty()) - loadBigFilesFromDirectory(installPath, "*.big"); -#endif -} - -void StdBIGFileSystem::reset() { -} - -void StdBIGFileSystem::update() { -} - -void StdBIGFileSystem::postProcessLoad() { -} - -ArchiveFile * StdBIGFileSystem::openArchiveFile(const Char *filename) { - File *fp = TheLocalFileSystem->openFile(filename, File::READ | File::BINARY); - AsciiString archiveFileName; - archiveFileName = filename; - archiveFileName.toLower(); - Int archiveFileSize = 0; - Int numLittleFiles = 0; - - ArchiveFile *archiveFile = NEW StdBIGFile(filename, AsciiString::TheEmptyString); - - DEBUG_LOG(("StdBIGFileSystem::openArchiveFile - opening BIG file %s", filename)); - - if (fp == nullptr) { - DEBUG_CRASH(("Could not open archive file %s for parsing", filename)); - return nullptr; - } - - AsciiString asciibuf; - char buffer[_MAX_PATH]; - fp->read(buffer, 4); // read the "BIG" at the beginning of the file. - buffer[4] = 0; - if (strcmp(buffer, BIGFileIdentifier) != 0) { - DEBUG_CRASH(("Error reading BIG file identifier in file %s", filename)); - fp->close(); - fp = nullptr; - return nullptr; - } - - // read in the file size. - fp->read(&archiveFileSize, 4); - - DEBUG_LOG(("StdBIGFileSystem::openArchiveFile - size of archive file is %d bytes", archiveFileSize)); - -// char t; - - // read in the number of files contained in this BIG file. - // change the order of the bytes cause the file size is in reverse byte order for some reason. - fp->read(&numLittleFiles, 4); - numLittleFiles = betoh(numLittleFiles); - - DEBUG_LOG(("StdBIGFileSystem::openArchiveFile - %d are contained in archive", numLittleFiles)); -// for (Int i = 0; i < 2; ++i) { -// t = buffer[i]; -// buffer[i] = buffer[(4-i)-1]; -// buffer[(4-i)-1] = t; -// } - - // seek to the beginning of the directory listing. - fp->seek(0x10, File::START); - // read in each directory listing. - ArchivedFileInfo *fileInfo = NEW ArchivedFileInfo; - - for (Int i = 0; i < numLittleFiles; ++i) { - Int filesize = 0; - Int fileOffset = 0; - fp->read(&fileOffset, 4); - fp->read(&filesize, 4); - - filesize = betoh(filesize); - fileOffset = betoh(fileOffset); - - fileInfo->m_archiveFilename = archiveFileName; - fileInfo->m_offset = fileOffset; - fileInfo->m_size = filesize; - - // read in the path name of the file. - Int pathIndex = -1; - do { - ++pathIndex; - fp->read(buffer + pathIndex, 1); - } while (buffer[pathIndex] != 0); - - Int filenameIndex = pathIndex; - while ((filenameIndex >= 0) && (buffer[filenameIndex] != '\\') && (buffer[filenameIndex] != '/')) { - --filenameIndex; - } - - fileInfo->m_filename = (char *)(buffer + filenameIndex + 1); - fileInfo->m_filename.toLower(); - buffer[filenameIndex + 1] = 0; - - AsciiString path; - path = buffer; - - AsciiString debugpath; - debugpath = path; - debugpath.concat(fileInfo->m_filename); -// DEBUG_LOG(("StdBIGFileSystem::openArchiveFile - adding file %s to archive file %s, file number %d", debugpath.str(), fileInfo->m_archiveFilename.str(), i)); - - archiveFile->addFile(path, fileInfo); - } - - archiveFile->attachFile(fp); - - delete fileInfo; - fileInfo = nullptr; - - // leave fp open as the archive file will be using it. - - return archiveFile; -} - -void StdBIGFileSystem::closeArchiveFile(const Char *filename) { - // Need to close the specified big file - ArchiveFileMap::iterator it = m_archiveFileMap.find(filename); - if (it == m_archiveFileMap.end()) { - return; - } - - if (stricmp(filename, MUSIC_BIG) == 0) { - // Stop the current audio - TheAudio->stopAudio(AudioAffect_Music); - - // No need to turn off other audio, as the lookups will just fail. - } - DEBUG_ASSERTCRASH(stricmp(filename, MUSIC_BIG) == 0, ("Attempting to close Archive file '%s', need to add code to handle its shutdown correctly.", filename)); - - // may need to do some other processing here first. - - delete (it->second); - m_archiveFileMap.erase(it); -} - -void StdBIGFileSystem::closeAllArchiveFiles() { -} - -void StdBIGFileSystem::closeAllFiles() { -} - -Bool StdBIGFileSystem::loadBigFilesFromDirectory(AsciiString dir, AsciiString fileMask, Bool overwrite) { - - FilenameList filenameList; - TheLocalFileSystem->getFileListInDirectory(dir, "", fileMask, filenameList, TRUE); - - Bool actuallyAdded = FALSE; - FilenameListIter it = filenameList.begin(); - while (it != filenameList.end()) { -#if RTS_ZEROHOUR - // TheSuperHackers @bugfix bobtista 18/11/2025 Skip duplicate INIZH.big in Data\INI to prevent CRC mismatches. - // English, Chinese, and Korean SKUs shipped with two INIZH.big files (one in Run directory, one in Run\Data\INI). - // The DeleteFile cleanup doesn't work on EA App/Origin installs because the folder is not writable, so we skip loading it instead. - if (it->endsWithNoCase("Data\\INI\\INIZH.big") || it->endsWithNoCase("Data/INI/INIZH.big")) { - it++; - continue; - } -#endif - - ArchiveFile *archiveFile = openArchiveFile((*it).str()); - - if (archiveFile != nullptr) { - DEBUG_LOG(("StdBIGFileSystem::loadBigFilesFromDirectory - loading %s into the directory tree.", (*it).str())); - loadIntoDirectoryTree(archiveFile, overwrite); - m_archiveFileMap[(*it)] = archiveFile; - DEBUG_LOG(("StdBIGFileSystem::loadBigFilesFromDirectory - %s inserted into the archive file map.", (*it).str())); - actuallyAdded = TRUE; - } - - it++; - } - - return actuallyAdded; -} diff --git a/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp b/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp index d47e473f4d2..ee53db20549 100644 --- a/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp +++ b/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp @@ -304,10 +304,10 @@ Bool StdLocalFileSystem::getFileInfo(const AsciiString& filename, FileInfo *file // TODO: fix this to be win compatible (time since 1601) auto time = write_time.time_since_epoch().count(); + fileInfo->sizeHigh = file_size >> 32; + fileInfo->sizeLow = file_size & UINT32_MAX; fileInfo->timestampHigh = time >> 32; fileInfo->timestampLow = time & UINT32_MAX; - fileInfo->sizeHigh = file_size >> 32; - fileInfo->sizeLow = file_size & UINT32_MAX; return TRUE; } diff --git a/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFile.cpp b/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFile.cpp deleted file mode 100644 index 3deee01fd82..00000000000 --- a/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFile.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -////// Win32BIGFile.cpp ///////////////////////// -// Bryan Cleveland, August 2002 -///////////////////////////////////////////////////// - -#include "Common/LocalFile.h" -#include "Common/LocalFileSystem.h" -#include "Common/RAMFile.h" -#include "Common/StreamingArchiveFile.h" -#include "Common/GameMemory.h" -#include "Common/PerfTimer.h" -#include "Win32Device/Common/Win32BIGFile.h" - -//============================================================================ -// Win32BIGFile::Win32BIGFile -//============================================================================ - -Win32BIGFile::Win32BIGFile(AsciiString name, AsciiString path) - : m_name(name) - , m_path(path) -{ - -} - -//============================================================================ -// Win32BIGFile::~Win32BIGFile -//============================================================================ - -Win32BIGFile::~Win32BIGFile() -{ - -} - -//============================================================================ -// Win32BIGFile::openFile -//============================================================================ - -File* Win32BIGFile::openFile( const Char *filename, Int access ) -{ - const ArchivedFileInfo *fileInfo = getArchivedFileInfo(AsciiString(filename)); - - if (fileInfo == nullptr) { - return nullptr; - } - - RAMFile *ramFile = nullptr; - - if (BitIsSet(access, File::STREAMING)) - ramFile = newInstance( StreamingArchiveFile ); - else - ramFile = newInstance( RAMFile ); - - ramFile->deleteOnClose(); - if (ramFile->openFromArchive(m_file, fileInfo->m_filename, fileInfo->m_offset, fileInfo->m_size) == FALSE) { - ramFile->close(); - ramFile = nullptr; - return nullptr; - } - - if ((access & File::WRITE) == 0) { - // requesting read only access. Just return the RAM file. - return ramFile; - } - - // whoever is opening this file wants write access, so copy the file to the local disk - // and return that file pointer. - - constexpr size_t bufferSize = 0; - File *localFile = TheLocalFileSystem->openFile(filename, access, bufferSize); - if (localFile != nullptr) { - ramFile->copyDataToFile(localFile); - } - - ramFile->close(); - ramFile = nullptr; - - return localFile; -} - -//============================================================================ -// Win32BIGFile::closeAllFiles -//============================================================================ - -void Win32BIGFile::closeAllFiles() -{ - -} - -//============================================================================ -// Win32BIGFile::getName -//============================================================================ - -AsciiString Win32BIGFile::getName() -{ - return m_name; -} - -//============================================================================ -// Win32BIGFile::getPath -//============================================================================ - -AsciiString Win32BIGFile::getPath() -{ - return m_path; -} - -//============================================================================ -// Win32BIGFile::setSearchPriority -//============================================================================ - -void Win32BIGFile::setSearchPriority( Int new_priority ) -{ - -} - -//============================================================================ -// Win32BIGFile::close -//============================================================================ - -void Win32BIGFile::close() -{ - -} - -//============================================================================ -// Win32BIGFile::getFileInfo -//============================================================================ - -Bool Win32BIGFile::getFileInfo(const AsciiString& filename, FileInfo *fileInfo) const -{ - const ArchivedFileInfo *tempFileInfo = getArchivedFileInfo(filename); - - if (tempFileInfo == nullptr) { - return FALSE; - } - - TheLocalFileSystem->getFileInfo(AsciiString(m_file->getName()), fileInfo); - - // fill in the size info. Since the size can't be bigger than a JUNK file, the high Int will always be 0. - fileInfo->sizeHigh = 0; - fileInfo->sizeLow = tempFileInfo->m_size; - - return TRUE; -} - diff --git a/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFileSystem.cpp b/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFileSystem.cpp deleted file mode 100644 index b824dcaed57..00000000000 --- a/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFileSystem.cpp +++ /dev/null @@ -1,275 +0,0 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -//////// Win32BIGFileSystem.h /////////////////////////// -// Bryan Cleveland, August 2002 -///////////////////////////////////////////////////////////// - -#include "Common/AudioAffect.h" -#include "Common/ArchiveFile.h" -#include "Common/ArchiveFileSystem.h" -#include "Common/file.h" -#include "Common/GameAudio.h" -#include "Common/GameMemory.h" -#include "Common/LocalFileSystem.h" - -#if RTS_ZEROHOUR -#include "Common/Registry.h" -#endif - -#include "Win32Device/Common/Win32BIGFile.h" -#include "Win32Device/Common/Win32BIGFileSystem.h" -#include "Utility/endian_compat.h" - - -static const char *BIGFileIdentifier = "BIGF"; - -Win32BIGFileSystem::Win32BIGFileSystem() : ArchiveFileSystem() { -} - -Win32BIGFileSystem::~Win32BIGFileSystem() { -} - -void Win32BIGFileSystem::init() { - DEBUG_ASSERTCRASH(TheLocalFileSystem != nullptr, ("TheLocalFileSystem must be initialized before TheArchiveFileSystem.")); - if (TheLocalFileSystem == nullptr) { - return; - } - - loadBigFilesFromDirectory("", "*.big"); - -#if RTS_ZEROHOUR - // load original Generals assets - AsciiString installPath; - GetStringFromGeneralsRegistry("", "InstallPath", installPath ); - //@todo this will need to be ramped up to a crash for release - DEBUG_ASSERTCRASH(!installPath.isEmpty(), ("Be 1337! Go install Generals!")); - if (!installPath.isEmpty()) - loadBigFilesFromDirectory(installPath, "*.big"); -#endif -} - -void Win32BIGFileSystem::reset() { -} - -void Win32BIGFileSystem::update() { -} - -void Win32BIGFileSystem::postProcessLoad() { -} - -ArchiveFile * Win32BIGFileSystem::openArchiveFile(const Char *filename) { - File *fp = TheLocalFileSystem->openFile(filename, File::READ | File::BINARY); - AsciiString archiveFileName; - archiveFileName = filename; - archiveFileName.toLower(); - Int archiveFileSize = 0; - Int numLittleFiles = 0; - - DEBUG_LOG(("Win32BIGFileSystem::openArchiveFile - opening BIG file %s", filename)); - - if (fp == nullptr) { - DEBUG_CRASH(("Could not open archive file %s for parsing", filename)); - return nullptr; - } - - AsciiString asciibuf; - char buffer[_MAX_PATH]; - // read the "BIG" at the beginning of the file. - buffer[4] = 0; - if (fp->read(buffer, 4) != 4 || strcmp(buffer, BIGFileIdentifier) != 0) { - DEBUG_CRASH(("Error reading BIG file identifier in file %s", filename)); - fp->close(); - fp = nullptr; - return nullptr; - } - - // read in the file size. - if (fp->read(&archiveFileSize, 4) != 4) { - DEBUG_CRASH(("Error reading BIG file size in file %s", filename)); - fp->close(); - fp = nullptr; - return nullptr; - } - - DEBUG_LOG(("Win32BIGFileSystem::openArchiveFile - size of archive file is %d bytes", archiveFileSize)); - -// char t; - - // read in the number of files contained in this BIG file. - // change the order of the bytes cause the file size is in reverse byte order for some reason. - if (fp->read(&numLittleFiles, 4) != 4) { - DEBUG_CRASH(("Error reading BIG file count in file %s", filename)); - fp->close(); - fp = nullptr; - return nullptr; - } - numLittleFiles = betoh(numLittleFiles); - - DEBUG_LOG(("Win32BIGFileSystem::openArchiveFile - %d are contained in archive", numLittleFiles)); -// for (Int i = 0; i < 2; ++i) { -// t = buffer[i]; -// buffer[i] = buffer[(4-i)-1]; -// buffer[(4-i)-1] = t; -// } - - // seek to the beginning of the directory listing. - fp->seek(0x10, File::START); - // read in each directory listing. - ArchivedFileInfo *fileInfo = NEW ArchivedFileInfo; - // TheSuperHackers @fix Mauller 23/04/2025 Create new file handle when necessary to prevent memory leak - ArchiveFile *archiveFile = NEW Win32BIGFile(filename, AsciiString::TheEmptyString); - - Bool corrupt = FALSE; - for (Int i = 0; i < numLittleFiles; ++i) { - Int filesize = 0; - Int fileOffset = 0; - if (fp->read(&fileOffset, 4) != 4 || fp->read(&filesize, 4) != 4) { - corrupt = TRUE; - break; - } - - filesize = betoh(filesize); - fileOffset = betoh(fileOffset); - - fileInfo->m_archiveFilename = archiveFileName; - fileInfo->m_offset = fileOffset; - fileInfo->m_size = filesize; - - // read in the path name of the file. - // the name must terminate within the buffer - Int pathIndex = -1; - Bool terminated = FALSE; - while (++pathIndex < (Int)sizeof(buffer)) { - if (fp->read(buffer + pathIndex, 1) != 1) - break; - if (buffer[pathIndex] == 0) { - terminated = TRUE; - break; - } - } - if (!terminated) { - corrupt = TRUE; - break; - } - - Int filenameIndex = pathIndex; - while ((filenameIndex >= 0) && (buffer[filenameIndex] != '\\') && (buffer[filenameIndex] != '/')) { - --filenameIndex; - } - - fileInfo->m_filename = (char *)(buffer + filenameIndex + 1); - fileInfo->m_filename.toLower(); - buffer[filenameIndex + 1] = 0; - - AsciiString path; - path = buffer; - - AsciiString debugpath; - debugpath = path; - debugpath.concat(fileInfo->m_filename); -// DEBUG_LOG(("Win32BIGFileSystem::openArchiveFile - adding file %s to archive file %s, file number %d", debugpath.str(), fileInfo->m_archiveFilename.str(), i)); - - archiveFile->addFile(path, fileInfo); - } - - if (corrupt) { - DEBUG_CRASH(("Truncated or corrupt directory in BIG file %s", filename)); - delete archiveFile; - delete fileInfo; - fp->close(); - fp = nullptr; - return nullptr; - } - - archiveFile->attachFile(fp); - - delete fileInfo; - fileInfo = nullptr; - - // leave fp open as the archive file will be using it. - - return archiveFile; -} - -void Win32BIGFileSystem::closeArchiveFile(const Char *filename) { - // Need to close the specified big file - ArchiveFileMap::iterator it = m_archiveFileMap.find(filename); - if (it == m_archiveFileMap.end()) { - return; - } - - if (stricmp(filename, MUSIC_BIG) == 0) { - // Stop the current audio - TheAudio->stopAudio(AudioAffect_Music); - - // No need to turn off other audio, as the lookups will just fail. - } - DEBUG_ASSERTCRASH(stricmp(filename, MUSIC_BIG) == 0, ("Attempting to close Archive file '%s', need to add code to handle its shutdown correctly.", filename)); - - // may need to do some other processing here first. - - delete (it->second); - m_archiveFileMap.erase(it); -} - -void Win32BIGFileSystem::closeAllArchiveFiles() { -} - -void Win32BIGFileSystem::closeAllFiles() { -} - -Bool Win32BIGFileSystem::loadBigFilesFromDirectory(AsciiString dir, AsciiString fileMask, Bool overwrite) { - - FilenameList filenameList; - TheLocalFileSystem->getFileListInDirectory(dir, "", fileMask, filenameList, TRUE); - - Bool actuallyAdded = FALSE; - FilenameListIter it = filenameList.begin(); - while (it != filenameList.end()) { -#if RTS_ZEROHOUR - // TheSuperHackers @bugfix bobtista 18/11/2025 Skip duplicate INIZH.big in Data\INI to prevent CRC mismatches. - // English, Chinese, and Korean SKUs shipped with two INIZH.big files (one in Run directory, one in Run\Data\INI). - // The DeleteFile cleanup doesn't work on EA App/Origin installs because the folder is not writable, so we skip loading it instead. - if (it->endsWithNoCase("Data\\INI\\INIZH.big") || it->endsWithNoCase("Data/INI/INIZH.big")) { - it++; - continue; - } -#endif - - ArchiveFile *archiveFile = openArchiveFile((*it).str()); - - if (archiveFile != nullptr) { - DEBUG_LOG(("Win32BIGFileSystem::loadBigFilesFromDirectory - loading %s into the directory tree.", (*it).str())); - loadIntoDirectoryTree(archiveFile, overwrite); - m_archiveFileMap[(*it)] = archiveFile; - DEBUG_LOG(("Win32BIGFileSystem::loadBigFilesFromDirectory - %s inserted into the archive file map.", (*it).str())); - actuallyAdded = TRUE; - } - - it++; - } - - return actuallyAdded; -} diff --git a/Core/GameEngineDevice/Source/Win32Device/Common/Win32LocalFileSystem.cpp b/Core/GameEngineDevice/Source/Win32Device/Common/Win32LocalFileSystem.cpp index 43a6ca5cbdd..5ec6fd35680 100644 --- a/Core/GameEngineDevice/Source/Win32Device/Common/Win32LocalFileSystem.cpp +++ b/Core/GameEngineDevice/Source/Win32Device/Common/Win32LocalFileSystem.cpp @@ -60,7 +60,7 @@ File * Win32LocalFileSystem::openFile(const Char *filename, Int access, size_t b AsciiString dirName; string.nextToken(&token, "\\/"); dirName = token; - while ((token.find('.') == nullptr) || (string.find('.') != nullptr)) { + while (!string.isEmpty()) { createDirectory(dirName); string.nextToken(&token, "\\/"); dirName.concat('\\'); @@ -194,10 +194,10 @@ Bool Win32LocalFileSystem::getFileInfo(const AsciiString& filename, FileInfo *fi return FALSE; } - fileInfo->timestampHigh = findData.ftLastWriteTime.dwHighDateTime; - fileInfo->timestampLow = findData.ftLastWriteTime.dwLowDateTime; fileInfo->sizeHigh = findData.nFileSizeHigh; fileInfo->sizeLow = findData.nFileSizeLow; + fileInfo->timestampHigh = findData.ftLastWriteTime.dwHighDateTime; + fileInfo->timestampLow = findData.ftLastWriteTime.dwLowDateTime; FindClose(findHandle); diff --git a/Core/Tools/Autorun/autorun.cpp b/Core/Tools/Autorun/autorun.cpp index 2e5a428c2b3..4f3618dcf2a 100644 --- a/Core/Tools/Autorun/autorun.cpp +++ b/Core/Tools/Autorun/autorun.cpp @@ -119,7 +119,7 @@ #include "GameClient/GameText.h" #include "Common/UnicodeString.h" #include "Win32Device/Common/Win32LocalFileSystem.h" -#include "Win32Device/Common/Win32BIGFileSystem.h" +#include "Common/BIGFileSystem.h" #endif //----------------------------------------------------------------------------- @@ -449,7 +449,7 @@ int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpszCmd // create singletons TheFileSystem->init(); TheLocalFileSystem = new Win32LocalFileSystem; - TheArchiveFileSystem = new Win32BIGFileSystem; + TheArchiveFileSystem = new BIGFileSystem; TheGameText = CreateGameTextInterface(); TheGameText->init(); diff --git a/Core/Tools/MapCacheBuilder/Source/WinMain.cpp b/Core/Tools/MapCacheBuilder/Source/WinMain.cpp index ab545a07892..fe7fcf1bc5d 100644 --- a/Core/Tools/MapCacheBuilder/Source/WinMain.cpp +++ b/Core/Tools/MapCacheBuilder/Source/WinMain.cpp @@ -51,8 +51,8 @@ #include "Common/ThingFactory.h" #include "Common/FileSystem.h" +#include "Common/BIGFileSystem.h" #include "Win32Device/Common/Win32LocalFileSystem.h" -#include "Win32Device/Common/Win32BIGFileSystem.h" #include "Common/SubsystemInterface.h" #include "GameClient/MapUtil.h" #include "W3DDevice/Common/W3DModuleFactory.h" @@ -101,7 +101,6 @@ #include #include "Win32Device/GameClient/Win32Mouse.h" #include "Win32Device/Common/Win32LocalFileSystem.h" -#include "Win32Device/Common/Win32BIGFileSystem.h" #include "WWLib/trim.h" @@ -249,7 +248,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, TheFileSystem = new FileSystem; initSubsystem(TheLocalFileSystem, (LocalFileSystem*)new Win32LocalFileSystem); - initSubsystem(TheArchiveFileSystem, (ArchiveFileSystem*)new Win32BIGFileSystem); + initSubsystem(TheArchiveFileSystem, (ArchiveFileSystem*)new BIGFileSystem); INI ini; initSubsystem(TheWritableGlobalData, new GlobalData(), "Data\\INI\\Default\\GameData.ini", "Data\\INI\\GameData.ini"); initSubsystem(TheGameText, CreateGameTextInterface()); diff --git a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp index 772830f0f67..0eee5949dc0 100644 --- a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp @@ -93,6 +93,7 @@ static void ConvertShortMapPathToLongMapPath(AsciiString &mapName) if (!token.endsWithNoCase(".map")) { DEBUG_CRASH(("Invalid map name %s", mapName.str())); + return; } // remove the .map from the end. token.truncateBy(4); diff --git a/Generals/Code/GameEngineDevice/CMakeLists.txt b/Generals/Code/GameEngineDevice/CMakeLists.txt index dea3ff6c9ae..dbb41d0f7b4 100644 --- a/Generals/Code/GameEngineDevice/CMakeLists.txt +++ b/Generals/Code/GameEngineDevice/CMakeLists.txt @@ -70,8 +70,6 @@ set(GAMEENGINEDEVICE_SRC Include/W3DDevice/GameLogic/W3DGameLogic.h Include/W3DDevice/GameLogic/W3DGhostObject.h Include/W3DDevice/GameLogic/W3DTerrainLogic.h -# Include/Win32Device/Common/Win32BIGFile.h -# Include/Win32Device/Common/Win32BIGFileSystem.h Include/Win32Device/Common/Win32GameEngine.h # Include/Win32Device/Common/Win32LocalFile.h # Include/Win32Device/Common/Win32LocalFileSystem.h @@ -168,8 +166,6 @@ set(GAMEENGINEDEVICE_SRC Source/W3DDevice/GameLogic/W3DGameLogic.cpp Source/W3DDevice/GameLogic/W3DGhostObject.cpp Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp -# Source/Win32Device/Common/Win32BIGFile.cpp -# Source/Win32Device/Common/Win32BIGFileSystem.cpp Source/Win32Device/Common/Win32GameEngine.cpp # Source/Win32Device/Common/Win32LocalFile.cpp # Source/Win32Device/Common/Win32LocalFileSystem.cpp diff --git a/Generals/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h b/Generals/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h index b17369dc844..01f5b0af14f 100644 --- a/Generals/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h +++ b/Generals/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h @@ -37,7 +37,7 @@ #include "GameLogic/GameLogic.h" #include "GameNetwork/NetworkInterface.h" #include "MilesAudioDevice/MilesAudioManager.h" -#include "Win32Device/Common/Win32BIGFileSystem.h" +#include "Common/BIGFileSystem.h" #include "Win32Device/Common/Win32LocalFileSystem.h" #include "W3DDevice/Common/W3DModuleFactory.h" #include "W3DDevice/GameLogic/W3DGameLogic.h" @@ -93,7 +93,7 @@ inline ModuleFactory *Win32GameEngine::createModuleFactory() { return NEW W3DMod inline ThingFactory *Win32GameEngine::createThingFactory() { return NEW W3DThingFactory; } inline FunctionLexicon *Win32GameEngine::createFunctionLexicon() { return NEW W3DFunctionLexicon; } inline LocalFileSystem *Win32GameEngine::createLocalFileSystem() { return NEW Win32LocalFileSystem; } -inline ArchiveFileSystem *Win32GameEngine::createArchiveFileSystem() { return NEW Win32BIGFileSystem; } +inline ArchiveFileSystem *Win32GameEngine::createArchiveFileSystem() { return NEW BIGFileSystem; } inline ParticleSystemManager* Win32GameEngine::createParticleSystemManager(Bool dummy) { if (dummy) diff --git a/Generals/Code/Tools/GUIEdit/Source/GUIEdit.cpp b/Generals/Code/Tools/GUIEdit/Source/GUIEdit.cpp index 4d18b320cd8..d5c928ff52a 100644 --- a/Generals/Code/Tools/GUIEdit/Source/GUIEdit.cpp +++ b/Generals/Code/Tools/GUIEdit/Source/GUIEdit.cpp @@ -85,7 +85,7 @@ #include "Win32Device/GameClient/Win32Mouse.h" #include "Win32Device/GameClient/Win32DIKeyboard.h" #include "Win32Device/Common/Win32LocalFileSystem.h" -#include "Win32Device/Common/Win32BIGFileSystem.h" +#include "Common/BIGFileSystem.h" #include "resource.h" #include "WinMain.h" @@ -485,7 +485,7 @@ void GUIEdit::init() // create file system TheFileSystem = new FileSystem; TheLocalFileSystem = new Win32LocalFileSystem; - TheArchiveFileSystem = new Win32BIGFileSystem; + TheArchiveFileSystem = new BIGFileSystem; TheFileSystem->init(); //--------------------------------------------------------------------------- diff --git a/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp b/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp index b3287235361..9c5421c320d 100644 --- a/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp @@ -79,7 +79,7 @@ #include #include "Win32Device/GameClient/Win32Mouse.h" #include "Win32Device/Common/Win32LocalFileSystem.h" -#include "Win32Device/Common/Win32BIGFileSystem.h" +#include "Common/BIGFileSystem.h" static SubsystemInterfaceList TheSubsystemListRecord; @@ -316,7 +316,7 @@ BOOL CWorldBuilderApp::InitInstance() TheFileSystem = new FileSystem; initSubsystem(TheLocalFileSystem, (LocalFileSystem*)new Win32LocalFileSystem); - initSubsystem(TheArchiveFileSystem, (ArchiveFileSystem*)new Win32BIGFileSystem); + initSubsystem(TheArchiveFileSystem, (ArchiveFileSystem*)new BIGFileSystem); // Just for kicks, get the HINSTANCE that WOL would need // if we were going to use it, which we aren't. diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index a84c0935e98..05bea1c62ca 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -1160,9 +1160,13 @@ set(GAMEENGINE_SRC Include/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h Include/GameNetwork/GeneralsOnline/NextGenTransport.h Include/GameNetwork/GeneralsOnline/PluginInterfaces.h + Source/GameNetwork/GeneralsOnline/CredentialStore.cpp Source/GameNetwork/GeneralsOnline/DiscordRichPresence.cpp Source/GameNetwork/GeneralsOnline/NGMP_Helpers.cpp Source/GameNetwork/GeneralsOnline/NGMPGame.cpp + Source/GameNetwork/GeneralsOnline/ProxyLogin.cpp + Source/GameNetwork/GeneralsOnline/ServiceConnectionPolicy.cpp + Source/GameNetwork/GeneralsOnline/ServiceEndpointPolicy.cpp Source/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.cpp Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp Source/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h index d5798a48ec7..4d4765d7805 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h @@ -1088,12 +1088,6 @@ class InGameUI : public SubsystemInterface, public Snapshot DrawableID m_soloNexusSelectedDrawableID; ///< The drawable of the nexus, if only one angry mob is selected, otherwise, null -#if defined(GENERALS_ONLINE) - int m_lastFPS = -1; - int m_currentFPS = -1; - int64_t lastFPSUpdate = -1; -#endif - // ---------------------------------------------------------------------------------------------- // STATIC Protected Data ------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/CredentialStore.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/CredentialStore.h new file mode 100644 index 00000000000..ab3f7be5fae --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/CredentialStore.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +// One DPAPI-protected credentials.json holds the GO login token of every server and the proxy logins. +// Every save reads the whole file, changes its own entry and writes the file back, so no entry wipes another. +// When the file cannot be read, decrypted or written, one log line with the error code is written (no secrets), the value +// stays in memory for this session and nothing blocks. +namespace ServiceConnection +{ +// the file path is asked for on every use, so it follows the instance of a multi-instance debug build +void ConfigureCredentialStore(std::string (*pathProvider)()); +void SetCredentialLogSink(void (*sink)(const std::string&)); +// test hook: encryption and decryption fail with this error code, 0 turns it off +void SetCredentialCryptFailureForTest(unsigned long errorCode); + +// returns false when the value could not be saved to the file; it is then kept for this session +bool SaveProxyLogin(const std::string& proxyKey, const std::string& user, const std::string& password); +bool LoadProxyLogin(const std::string& proxyKey, std::string& user, std::string& password); +bool DeleteProxyLogin(const std::string& proxyKey); + +bool SaveRefreshToken(const std::string& serverKey, const std::string& token); +bool LoadRefreshToken(const std::string& serverKey, std::string& token); +bool DeleteRefreshToken(const std::string& serverKey); +} diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h index 0bcc1a2c3e3..7e5d2efd145 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h @@ -1,13 +1,14 @@ #pragma once -#include +#include +#include +#include +#include "GameNetwork/GeneralsOnline/ServiceEndpointPolicy.h" -enum EHTTPVersion +// One layer of data_packs in settings.json: BIG paths in priority order, the first one wins +struct GenOnlineDataPackLayer { - HTTP_VERSION_AUTO, - HTTP_VERSION_1_0, - HTTP_VERSION_1_1, - HTTP_VERSION_2_0, - HTTP_VERSION_3_0 + bool m_enabled = true; + std::vector m_includePaths; }; class GenOnlineSettings @@ -15,7 +16,6 @@ class GenOnlineSettings public: GenOnlineSettings(); - float Camera_MoveSpeedRatio() const { return m_Camera_MoveSpeedRatio; } float Camera_GetMinHeight() const { return m_Camera_MinHeight; } float Camera_GetMaxHeight_WhenLobbyHost() const { return m_Camera_MaxHeight_LobbyHost; } @@ -37,7 +37,6 @@ class GenOnlineSettings } } - bool Graphics_DrawStatsOverlay() const { return m_Render_DrawStatsOverlay; } bool Graphics_LimitFramerate() const { return m_Render_LimitFramerate; } int Graphics_GetFPSLimit() const { @@ -70,62 +69,32 @@ class GenOnlineSettings Load(); } - bool DataPacks_UseCommunityPatch() const { return m_DataPacks_UseCommunityPatch; } + const GenOnlineDataPackLayer& DataPacks_User() const { return m_DataPacks_User; } + const GenOnlineDataPackLayer& DataPacks_GeneralsOnline() const { return m_DataPacks_GeneralsOnline; } + const GenOnlineDataPackLayer& DataPacks_CommunityPatch() const { return m_DataPacks_CommunityPatch; } + const std::vector& DataPacks_UserExcludePaths() const { return m_DataPacks_UserExcludePaths; } - bool Network_UseAlternativeEndpoint() const { return m_Network_UseAlternativeEndpoint; } - EHTTPVersion Network_GetHTTPVersion() const { return m_Network_HTTPVersion; } - int Network_GetHTTPVersionForCurl() const - { - switch (m_Network_HTTPVersion) - { - case HTTP_VERSION_AUTO: - { - return CURL_HTTP_VERSION_NONE; - } - - case HTTP_VERSION_1_0: - { - return CURL_HTTP_VERSION_1_0; - } - - case HTTP_VERSION_1_1: - { - return CURL_HTTP_VERSION_1_1; - } - - case HTTP_VERSION_2_0: - { - return CURL_HTTP_VERSION_2_0; - } - - case HTTP_VERSION_3_0: - { - return CURL_HTTP_VERSION_3; - } - } - - return CURL_HTTP_VERSION_NONE; - } + ServiceConnectionSettings Network_GetConnectionSettings() const { std::lock_guard lock(m_NetworkMutex); return m_ConnectionSettings; } + std::string Network_GetLastRoute() const { std::lock_guard lock(m_NetworkMutex); return m_LastRoute; } + bool Network_SaveLastRoute(const std::string& host); + bool Network_SaveWindowsLogin(bool windowsLogin); + bool Network_SaveConnectionSettings(const ServiceConnectionSettings& settings, std::string& error); private: void Load(void); - void Save(); + bool Save(); private: // NOTE: This also works as the default creation (since we just call Save) const float m_Camera_MinHeight_default = 100.f; float m_Camera_MinHeight = m_Camera_MinHeight_default; - const float m_Camera_MoveSpeedRatio_default = 1.f; - float m_Camera_MoveSpeedRatio = m_Camera_MoveSpeedRatio_default; - float m_Camera_MaxHeight_LobbyHost = GENERALS_ONLINE_DEFAULT_LOBBY_CAMERA_ZOOM; bool m_bInitialized = false; bool m_bVerbose = false; - bool m_Render_DrawStatsOverlay = true; bool m_Render_LimitFramerate = true; int m_Render_FramerateLimit_FPSVal = 60; int m_Chat_LifeSeconds = 30; @@ -139,10 +108,19 @@ class GenOnlineSettings bool m_Social_Notification_PlayerSendsRequest_Menus = true; bool m_Social_Notification_PlayerSendsRequest_Gameplay = true; - bool m_DataPacks_UseCommunityPatch = true; + // BIG archives above the retail ones. The layers load in this order, highest priority first. A missing or invalid field keeps its default. + GenOnlineDataPackLayer m_DataPacks_User; + std::vector m_DataPacks_UserExcludePaths; // loose files of the game folder, BIGs of the game folders and BIGs of the user layer that are left out + GenOnlineDataPackLayer m_DataPacks_GeneralsOnline = { true, { "user:GeneralsOnlineGameData/GeneralsOnline_Core.big" } }; + GenOnlineDataPackLayer m_DataPacks_CommunityPatch = { true, { "user:GeneralsOnlineGameData/500_900_CommunityPatch_CoreINI.big" } }; std::string m_Plugins_Anticheat = std::string(); - EHTTPVersion m_Network_HTTPVersion = EHTTPVersion::HTTP_VERSION_AUTO; - bool m_Network_UseAlternativeEndpoint = false; + mutable std::mutex m_NetworkMutex; + ServiceConnectionSettings m_ConnectionSettings; + // host of the service route that worked last + std::string m_LastRoute; + // keeps keys this build does not know when a setting is saved mid-session + std::string m_PreservedJSON = "{}"; + bool m_CanSave = true; }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPManager.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPManager.h index 57a4c498928..1b51834ee49 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPManager.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPManager.h @@ -47,23 +47,18 @@ class HTTPManager return m_sProtocolInUse.load(); } - void AddHandleToMulti(CURL* pNewHandle); + bool AddHandleToMulti(CURL* pNewHandle); void RemoveHandleFromMulti(CURL* pHandleToRemove); void SendGETRequest(const char* szURI, EIPProtocolVersion protover, std::map& inHeaders, std::function completionCallback, std::function progressCallback = nullptr, int timeoutMS = -1); // NOTE: set bDisableServiceAuth for endpoints that must be called with something other than the session token (e.g. the refresh endpoint, which needs the refresh token), otherwise the session token overwrites any Authorization header passed in inHeaders void SendPOSTRequest(const char* szURI, EIPProtocolVersion protover, std::map& inHeaders, const char* szPostData, std::function completionCallback, std::function progressCallback = nullptr, int timeoutMS = -1, bool bDisableServiceAuth = false); void SendPUTRequest(const char* szURI, EIPProtocolVersion protover, std::map& inHeaders, const char* szData, std::function completionCallback, std::function progressCallback = nullptr, int timeoutMS = -1); - void SendS3PUTRequest(const char* szURI, EIPProtocolVersion protover, std::map& inHeaders, std::vector vecBuffer, std::function completionCallback, std::function progressCallback = nullptr, int timeoutMS = -1); + void SendS3PUTRequest(const char* szURI, EIPProtocolVersion protover, std::map& inHeaders, std::vector vecBuffer, std::function completionCallback, std::function progressCallback = nullptr, int timeoutMS = -1, int maxSendBytesPerSec = 0); void SendDELETERequest(const char* szURI, EIPProtocolVersion protover, std::map& inHeaders, const char* szData, std::function completionCallback, std::function progressCallback = nullptr, int timeoutMS = -1); void Shutdown(); - bool IsProxyEnabled() const { return m_bProxyEnabled; } - - bool DeterminePlatformProxySettings(); - std::string& GetProxyAddress() { return m_strProxyAddr; } - uint16_t GetProxyPort() const { return m_proxyPort; } private: HTTPRequest* PlatformCreateRequest(EHTTPVerb htpVerb, EIPProtocolVersion protover, const char* szURI, std::map& inHeaders, std::function completionCallback, @@ -74,9 +69,6 @@ class HTTPManager std::atomic m_sProtocolInUse = EIPProtocolVersion::DONT_CARE; - bool m_bProxyEnabled = false; - std::string m_strProxyAddr; - uint16_t m_proxyPort; std::atomic m_bShuttingDown = false; diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h index f5b901aade5..d2256ea667e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include "../ServiceConnectionPolicy.h" enum class EHTTPVerb; enum class EIPProtocolVersion; @@ -30,12 +32,18 @@ class HTTPRequest void SetPostData(const char* szPostData); void SetPostDataBuffer(std::vector vecBuffer); void StartRequest(); + void PollConnectionSetup(); void DisableServiceAuth() { m_bAppendAuthIfPresent = false; } + void SetMaxSendSpeed(int bytesPerSec) + { + m_maxSendBytesPerSec = bytesPerSec; + } + void OnResponsePartialWrite(std::uint8_t* pBuffer, size_t numBytes); bool HasStarted() const { return m_bIsStarted; } @@ -65,18 +73,30 @@ class HTTPRequest std::string GetURI() { return m_strURI; } + // route the finished request went through + ServiceConnection::ProxyRoute GetProxyRoute() const { return m_proxyRoute < m_proxyPolicy.routes.size() ? m_proxyPolicy.routes[m_proxyRoute] : ServiceConnection::ProxyRoute(); } + private: void PlatformStartRequest(); private: CURL* m_pCURL = nullptr; + bool m_handleAdded = false; + std::shared_ptr m_proxyResolution; + ServiceConnection::ResolvedProxyPolicy m_proxyPolicy; + std::string m_setupFailurePhase; + size_t m_proxyRoute = 0; + std::chrono::steady_clock::time_point m_setupStarted; - int m_responseCode = -1; + long m_responseCode = 0; + CURLcode m_result = CURLE_OK; EHTTPVerb m_httpVerb; bool m_bAppendAuthIfPresent = true; + int m_maxSendBytesPerSec = 0; // 0 = unlimited + EIPProtocolVersion m_protover; int m_timeoutMS = 5000; diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Auth.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Auth.h index d7c524879e7..9deb4599bf2 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Auth.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Auth.h @@ -23,6 +23,7 @@ class NGMP_OnlineServices_AuthInterface void RefreshToken(); void BeginLogin(); + void BeginLoginOnSelectedRoute(); void DoFullLoginFlow(); void Tick(); @@ -48,6 +49,8 @@ class NGMP_OnlineServices_AuthInterface void LogoutOfMyAccount(); + static std::string GetCredentialsFilePath(); + private: void LoginAsSecondaryDevAccount(); @@ -56,7 +59,7 @@ class NGMP_OnlineServices_AuthInterface void OnRefreshTokenFailed(const char* szReason, const std::string& strBody = std::string()); - std::string GetCredentialsFilePath(); + static std::string CredentialServerKey(); private: bool m_bWaitingLogin = false; diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h index 7d1419d2ee1..7420f4143b2 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h @@ -1,9 +1,11 @@ #pragma once +#include "ServiceConnectionPolicy.h" #include "NGMP_include.h" #include #include +#include class HTTPManager; @@ -28,6 +30,7 @@ struct S3ScreenshotEntry std::vector vecBytes; std::string strSignedURI; EScreenshotType screenshotType; + std::chrono::steady_clock::time_point notBefore; // upload is held back until this time bool operator==(const S3ScreenshotEntry& other) const { @@ -200,6 +203,9 @@ class WebSocket // Queues the message; Tick() flushes it on the main thread. Thread-safe. void Send(const char* message); + // Continues the initial connect after the proxy login prompt closed. + void ResumeAfterProxyLogin(bool loggedIn); + // Thread-safe queue of inbound P2P signal payloads; drained by CSignalingClient::Poll(). void PushPendingSignal(std::vector payload) { @@ -224,6 +230,16 @@ class WebSocket } private: + CURLcode ReceiveOneFrame(); + void PollConnectionSetup(); + void StartResolvedConnection(); + void FailConnectionSetup(); + bool m_proxyPrompted = false; + std::shared_ptr m_proxyResolution; + ServiceConnection::ResolvedProxyPolicy m_proxyPolicy; + size_t m_proxyRoute = 0; + int64_t m_proxySetupStarted = -1; + bool m_connectAsReconnect = false; CURL* m_pCurlWS = nullptr; CURLM* m_pMulti = nullptr; struct curl_slist* m_pHeaders = nullptr; @@ -259,7 +275,19 @@ class WebSocket // Outbound messages queued for Tick() to flush. std::mutex m_outboundQueueMutex; - std::vector m_vecQueuedOutboungMsgs; + struct OutboundMessage + { + std::string payload; + bool heartbeat; + }; + std::vector m_vecQueuedOutboungMsgs; + size_t m_queuedOutboundBytes = 0; + size_t m_outboundOffset = 0; + bool m_heartbeatQueued = false; + bool m_acceptOutbound = true; + void QueueOutbound(std::string payload, bool heartbeat); + void ClearOutbound(); + CURLcode FlushOutbound(); std::mutex m_pendingSignalsMutex; std::queue> m_pendingSignals; @@ -341,6 +369,8 @@ struct ServiceConfig int screenshot_width = 557; int screenshot_height = 333; + int screenshot_upload_bytes_per_sec = 131072; // 128 KB/s + int screenshot_slot_offset_ms = 2000; // ICE client used for P2P NAT traversal: 0 = library default, 1 = native, 2 = WebRTC. int ice_implementation = 2; @@ -351,7 +381,7 @@ struct ServiceConfig ra_slack_percent, frame_grouping_frames, enable_host_migration, network_do_immediate_flush_per_frame, network_send_flags, network_latency_logic_model, use_default_config, ra_slack_override_percent_in_default, do_probes, do_replay_upload, network_mesh_histogram_duration, ibra_ra_tweaks, ibra_minslack_default, ibra_maxslack_default, ibra_minslack_greaterthan300ms, ibra_maxslack_greaterthan300ms, ibra_minslack_greaterthan200ms, ibra_maxslack_greaterthan200ms, - screenshot_width, screenshot_height, ice_implementation, use_new_networking) + screenshot_width, screenshot_height, screenshot_upload_bytes_per_sec, screenshot_slot_offset_ms, ice_implementation, use_new_networking) }; class NGMP_OnlineServicesManager @@ -522,6 +552,12 @@ class NGMP_OnlineServicesManager } void StartVersionCheck(std::function fnCallback); + void StartVersionCheckOnSelectedRoute(std::function fnCallback); + void EnsureServiceRoute(std::function callback); + // true when the last failed route check was a proxy login the player did not complete + bool RouteFailedProxyAuth() const { return m_routeProxyAuthFailed; } + static bool IsServiceURLTrusted(const std::string& url); + static bool IsWebSocketURLTrusted(const std::string& url); std::shared_ptr Internal_GetWebSocket() const { return m_pWebSocket; } HTTPManager* GetHTTPManager() const { return m_pHTTPManager; } @@ -547,6 +583,12 @@ class NGMP_OnlineServicesManager void ProcessMOTD(const char* szMOTD) { m_strMOTD = std::string(szMOTD); + // the welcome screen shows which custom server this is, ahead of anything the server says + const ServiceConnectionSettings settings = Settings.Network_GetConnectionSettings(); + if (settings.serverMode == "custom") + { + m_strMOTD = "Custom server: " + settings.serverAddress.substr(settings.serverAddress.find("://") + 3) + "\n \n" + m_strMOTD; + } } std::string& GetMOTD() { return m_strMOTD; } @@ -586,10 +628,10 @@ class NGMP_OnlineServicesManager ServiceConfig& GetServiceConfig() { return m_ServiceConfig; } public: - void CacheScreenshotBytes_StartMatch(std::vector& vecData) + void CacheScreenshotBytes_StartMatch(std::vector vecData) { std::scoped_lock ssLock(m_ScreenshotMutex); - m_vecCachedScreenshotBytes_MatchStart = vecData; + m_vecCachedScreenshotBytes_MatchStart = std::move(vecData); } void CacheScreenshotBytes_EndMatch(uint64_t matchID, std::vector data); @@ -636,6 +678,17 @@ class NGMP_OnlineServicesManager std::vector m_vecScreenshotThreads; std::mutex m_mutexScreenshotThreads; + void ProbeServiceRoute(); + void FinishServiceRoute(bool success); + std::string m_serviceBase; + std::vector m_bootstrapRoutes; + size_t m_bootstrapIndex = 0; + std::vector> m_routeCallbacks; + std::chrono::steady_clock::time_point m_routeValidatedAt{}; + std::string m_routeSettingsKey; + bool m_routeProxyPrompted = false; + bool m_routeProxyAuthFailed = false; + ServiceConfig m_ServiceConfig; HTTPManager* m_pHTTPManager = nullptr; diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/ProxyLogin.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/ProxyLogin.h new file mode 100644 index 00000000000..34eed0176c3 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/ProxyLogin.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include "GameNetwork/GeneralsOnline/ServiceEndpointPolicy.h" + +namespace ServiceConnection +{ +struct ProxyRoute; + +bool IsProxyLoginOpen(); +// Worker threads hand work back to the main thread here; it runs once per frame from the game engine update +void RunOnMainThread(std::function task); +void PumpMainThreadTasks(); + +// Main thread only. One dialog does both jobs: it asks for the login of a proxy that answered 407 and checks it in place. +// Login path: done(true) once the proxy accepted the login (or the Windows login was chosen), done(false) on cancel. +// The Windows login is offered only where the route can use it; a route that already tried it says it was not accepted +void RequestProxyLogin(const ProxyRoute& route, std::function done); +// Accept in Options with changed connection settings: tests them first. +// Save: they passed, or the player chose to save anyway. KeepEditing: back to the options. Discard: leave the connection settings as they were. +enum class AcceptChoice { Save, KeepEditing, Discard }; +void ShowAcceptTest(const ServiceConnectionSettings& settings, std::function decided); +} diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/ServiceConnectionPolicy.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/ServiceConnectionPolicy.h new file mode 100644 index 00000000000..f33ca348a74 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/ServiceConnectionPolicy.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "GameNetwork/GeneralsOnline/ServiceEndpointPolicy.h" + +namespace ServiceConnection +{ +struct ProxyRoute +{ + std::string url; + bool direct = true; + bool allowIntegratedAuth = false; + // the Windows login can be used here when the player wants it; never for SOCKS + bool integratedAuthPossible = false; +}; + +struct ResolvedProxyPolicy +{ + std::vector routes; + std::string error; + unsigned long nativeError = 0; + std::string failurePhase; + std::string fallbackReason; + // what the policy comes from, for display: a host:port, "PAC: ", "Auto-detect" or "Direct" + std::string source; +}; + +struct ProxyResolution +{ + std::atomic ready{false}; + ResolvedProxyPolicy result; +}; +// Successful system policies expire after 60 seconds; settings saves invalidate immediately. +void InvalidateProxyPolicyCache(); +bool ShouldLogProxyPolicy(bool websocket, const ResolvedProxyPolicy& result); +std::shared_ptr StartProxyResolution(ServiceConnectionSettings settings, std::string destinationURL); + +// Worker-only: PAC/WPAD resolution can take up to five seconds. Only absent optional WPAD permits system fallback. +ResolvedProxyPolicy ResolveProxyPolicy(const ServiceConnectionSettings& settings, const std::string& destinationURL); +bool ValidateProxyURL(const std::string& proxyURL, std::string& error); +// Configures policy only. The caller owns URL, transfer deadlines and ordered route retries. +bool ConfigureCurl(CURL* curl, const std::string& destinationURL, const ProxyRoute& route, std::string& error); +bool SaveProxyCredentials(const std::string& proxyURL, const std::string& username, + const std::string& password, std::string& error); +bool DeleteProxyCredentials(const std::string& proxyURL, std::string& error); +bool HasProxyCredentials(const std::string& proxyURL); +// Login kept in memory for this session only; never written to disk +void SetSessionProxyCredentials(const std::string& proxyURL, const std::string& username, const std::string& password); +void ClearSessionProxyCredentials(const std::string& proxyURL); +// Logins are bound to one proxy: nothing typed for another proxy is ever offered to this one +void ClearSessionProxyCredentialsExcept(const std::string& keepProxyURL); +// The key a proxy's login is stored under: scheme://host:port in lower case with the default port filled in, empty for an invalid address +std::string CanonicalProxyKey(const std::string& proxyURL); +// User name of the session or saved login, empty when there is none +std::string ProxyUsername(const std::string& proxyURL); +// host:port of a proxy address for display +std::string ProxyDisplayName(const std::string& proxyURL); + +struct ConnectionTestResult +{ + enum class Outcome { Connected, ProxyAuthRequired, ServiceRejected, Unreachable, Failed }; + Outcome outcome = Outcome::Failed; + std::string route; + std::string proxyURL; + std::string detail; + bool proxyAnswered = false; + bool proxyIntegratedAuthPossible = false; +}; +// Worker-only: probes the anonymous VersionManifest request with the given settings +ConnectionTestResult TestServiceConnection(const ServiceConnectionSettings& settings, const std::string& environment, int maxSeconds = 15); +std::string DescribeConnectionTest(const ConnectionTestResult& result); +const char* ProtocolName(long version); +const char* FailurePhase(CURLcode code, long status, long proxyStatus); +} \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/ServiceEndpointPolicy.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/ServiceEndpointPolicy.h new file mode 100644 index 00000000000..a97f070cc0e --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/ServiceEndpointPolicy.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include + +struct ServiceConnectionSettings +{ + // "automatic" picks a working route, "custom" uses only serverAddress + std::string serverMode = "automatic"; + // https origin of the custom server, empty unless serverMode is custom + std::string serverAddress; + // "automatic" follows the Windows proxy settings, "disabled" connects directly, "custom" uses proxyURL + std::string proxyMode = "automatic"; + std::string proxyURL; + // try the current Windows account when the proxy asks for a login + bool windowsLogin = true; +}; + +namespace ServiceConnection +{ +// host[:port] or https://host[:port] to its origin (https, lower case host, default port dropped); false with a reason for anything else +bool NormalizeCustomEndpoint(const std::string& typed, std::string& origin, std::string& error); +// The key the login token is stored under: one for every GO route, one per custom origin, empty when a custom server has no valid address +std::string CredentialServerKey(const ServiceConnectionSettings& settings); +// remembered is the host of the route that worked last, ignored unless it is one of the automatic routes +std::vector BootstrapRoutes(const std::string& environment, + const ServiceConnectionSettings& settings, const std::string& remembered); +// host name of a route url +std::string RouteHost(const std::string& route); +bool IsTrustedServiceURL(const std::string& url, const std::string& base, bool websocket); +// wss address of the websocket on a service base: the scheme swapped from https and ws appended to the path; empty for a base that is not https +std::string WebSocketURLForBase(const std::string& base); +std::string RequestURLForLog(const std::string& url, const std::string& serviceBase); +bool IsExpectedVersionManifest(const std::string& body); +// Verified TLS (not in _DEBUG builds), no redirects, no embedded credentials, direct connection; HTTPS or WSS only. +bool ConfigureServiceTransport(CURL* curl, const std::string& url, std::string& error); +} diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index 7a43b85c8ca..3e1f2f91917 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -93,6 +93,7 @@ static void ConvertShortMapPathToLongMapPath(AsciiString &mapName) if (!token.endsWithNoCase(".map")) { DEBUG_CRASH(("Invalid map name %s", mapName.str())); + return; } // remove the .map from the end. token.truncateBy(4); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 0c497745de0..e9d3dfe39d6 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -112,6 +112,7 @@ // GENERALS ONLINE #include "../OnlineServices_Init.h" +#include "GameNetwork/GeneralsOnline/ProxyLogin.h" #include "GameNetwork/GeneralsOnline/DiscordRichPresence.h" #include "GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h" #include "GameNetwork/GameSpyOverlay.h" @@ -504,6 +505,10 @@ void GameEngine::init() #endif///////////////////////////////////////////////////////////////////////////////////////////// + // NGMP_CHANGE: Init our settings before the archives are mounted, the data_packs layers decide which ones. Needs TheGlobalData for the user data path. + NGMP_OnlineServicesManager::Settings.Initialize(); + TheFileSystem->setExcludedPaths(NGMP_OnlineServicesManager::Settings.DataPacks_UserExcludePaths()); + initSubsystem(TheArchiveFileSystem, "TheArchiveFileSystem", createArchiveFileSystem(), nullptr); // this MUST come after TheLocalFileSystem creation #ifdef DUMP_PERF_STATS/////////////////////////////////////////////////////////////////////////// @@ -538,9 +543,6 @@ void GameEngine::init() // special-case: parse command-line parameters after loading global data CommandLine::parseCommandLineForEngineInit(); - // NGMP_CHANGE: Init our settings before loadMods, which reads DataPacks_UseCommunityPatch. Needs TheGlobalData for the user data path. - NGMP_OnlineServicesManager::Settings.Initialize(); - TheArchiveFileSystem->loadMods(); // doesn't require resets so just create a single instance here. @@ -1011,6 +1013,9 @@ void GameEngine::update() } + // work handed back by worker threads, also while no online services instance exists + ServiceConnection::PumpMainThreadTasks(); + if (NGMP_OnlineServicesManager::GetInstance() != nullptr) { NGMP_OnlineServicesManager::GetInstance()->Tick(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index 9f33223decf..985ace24080 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -66,6 +66,12 @@ #include "GameClient/GUICallbacks.h" #include "GameClient/GlobalLanguage.h" #include "GameNetwork/FirewallHelper.h" +#if defined(GENERALS_ONLINE) +#include "GameNetwork/GeneralsOnline/ServiceConnectionPolicy.h" +#include "GameNetwork/GeneralsOnline/ProxyLogin.h" +#include "GameClient/GadgetPushButton.h" +#include +#endif #include "GameNetwork/IPEnumeration.h" #include "GameNetwork/GameSpyOverlay.h" #include "GameNetwork/GameSpy/PeerDefs.h" @@ -220,6 +226,427 @@ WindowLayout *OptionsLayout = nullptr; static OptionPreferences *pref = nullptr; +#if defined(GENERALS_ONLINE) +// Network section: the Online and LAN tabs and their controls come from OptionsMenu.wnd. A control that is missing is skipped. +struct NetworkControls +{ + GameWindow *tabOnline = nullptr; + GameWindow *tabLan = nullptr; + GameWindow *panelOnline = nullptr; + GameWindow *panelLan = nullptr; + GameWindow *proxyModeLabel = nullptr; + GameWindow *proxyMode = nullptr; + GameWindow *proxyAddressLabel = nullptr; + GameWindow *proxyAddress = nullptr; + GameWindow *serverLabel = nullptr; + GameWindow *server = nullptr; + GameWindow *serverAddressLabel = nullptr; + GameWindow *serverAddress = nullptr; + GameWindow *lanLabel = nullptr; +}; + +static GameWindow *networkControl(const char *name) +{ + return TheWindowManager->winGetWindowFromId(nullptr, TheNameKeyGenerator->nameToKey(name)); +} + +static NetworkControls getNetworkControls() +{ + NetworkControls c; + c.tabOnline = networkControl("OptionsMenu.wnd:ButtonTabOnline"); + c.tabLan = networkControl("OptionsMenu.wnd:ButtonTabLan"); + c.panelOnline = networkControl("OptionsMenu.wnd:PanelOnline"); + c.panelLan = networkControl("OptionsMenu.wnd:PanelLan"); + c.proxyModeLabel = networkControl("OptionsMenu.wnd:StaticTextProxyMode"); + c.proxyMode = networkControl("OptionsMenu.wnd:ComboBoxProxyMode"); + c.proxyAddressLabel = networkControl("OptionsMenu.wnd:StaticTextProxyAddress"); + c.proxyAddress = networkControl("OptionsMenu.wnd:TextEntryProxyAddress"); + c.serverLabel = networkControl("OptionsMenu.wnd:StaticTextServer"); + c.server = networkControl("OptionsMenu.wnd:ComboBoxServer"); + c.serverAddressLabel = networkControl("OptionsMenu.wnd:StaticTextServerAddress"); + c.serverAddress = networkControl("OptionsMenu.wnd:TextEntryServerAddress"); + c.lanLabel = networkControl("OptionsMenu.wnd:StaticTextLANIpAddresses"); + return c; +} + +static bool networkTestPassed = false; +static bool networkDiscard = false; +static bool networkLocked = false; + +static UnicodeString networkWide(const std::string &text) +{ + UnicodeString result; + result.translate(text.c_str()); + return result; +} + +static std::string networkNarrow(const UnicodeString &text) +{ + AsciiString ascii; + ascii.translate(text); + return ascii.str(); +} + +static std::string networkTrimmed(std::string text) +{ + text.erase(0, text.find_first_not_of(" \t")); + text.erase(text.find_last_not_of(" \t") + 1); + return text; +} + +static void networkSetText(GameWindow *label, const wchar_t *text) +{ + if (label != nullptr) + { + GadgetStaticTextSetText(label, UnicodeString(text)); + } +} + +static void networkSetTooltip(GameWindow *window, const wchar_t *text) +{ + if (window != nullptr) + { + window->winSetTooltip(UnicodeString(text)); + } +} + +static void networkFillCombo(GameWindow *combo, std::initializer_list entries, Int selected) +{ + if (combo == nullptr) + { + return; + } + const Color white = GameMakeColor(255, 255, 255, 255); + GadgetComboBoxReset(combo); + for (const wchar_t *entry : entries) + { + GadgetComboBoxAddEntry(combo, UnicodeString(entry), white); + } + GadgetComboBoxSetSelectedPos(combo, selected); +} + +static void networkShowTab(bool online) +{ + const NetworkControls c = getNetworkControls(); + if (c.panelOnline != nullptr) + { + c.panelOnline->winHide(!online); + } + if (c.panelLan != nullptr) + { + c.panelLan->winHide(online); + } + if (c.tabOnline != nullptr) + { + GadgetCheckLikeButtonSetVisualCheck(c.tabOnline, online); + } + if (c.tabLan != nullptr) + { + GadgetCheckLikeButtonSetVisualCheck(c.tabLan, !online); + } +} + +// The address entries show what the choice resolves to while they can't be edited; what was typed for Custom is kept meanwhile. +static std::string networkProxyCustomText; +static std::string networkServerCustomText; +static bool networkProxyEntryCustom = false; +static bool networkServerEntryCustom = false; +static std::shared_ptr networkSystemProxy; +static std::string networkSystemProxyText; + +static const char *networkEnvironment() +{ + return NGMP_OnlineServicesManager::g_Environment == NGMP_OnlineServicesManager::EEnvironment::DEV ? "dev" : + NGMP_OnlineServicesManager::g_Environment == NGMP_OnlineServicesManager::EEnvironment::TEST ? "test" : "prod"; +} + +// the host the Automatic server choice uses, the route that worked last first +static std::string networkServerHost() +{ + ServiceConnectionSettings settings; + const auto routes = ServiceConnection::BootstrapRoutes(networkEnvironment(), settings, NGMP_OnlineServicesManager::Settings.Network_GetLastRoute()); + if (routes.empty()) + { + return std::string(); + } + std::string host = routes.front(); + host.erase(0, host.find("://") == std::string::npos ? 0 : host.find("://") + 3); + return host.substr(0, host.find('/')); +} + +// the Windows proxy policy is looked up on a worker thread, networkPoll picks the result up +static void networkStartSystemLookup() +{ + ServiceConnectionSettings system; + system.proxyMode = "automatic"; + ServiceConnectionSettings selected = NGMP_OnlineServicesManager::Settings.Network_GetConnectionSettings(); + const auto routes = ServiceConnection::BootstrapRoutes(networkEnvironment(), selected, NGMP_OnlineServicesManager::Settings.Network_GetLastRoute()); + networkSystemProxyText.clear(); + networkSystemProxy = ServiceConnection::StartProxyResolution(system, routes.empty() ? std::string("https://api.playgenerals.online/") : routes.front()); +} + +// the address entries only matter for the Custom choice +static void networkRefresh() +{ + const NetworkControls c = getNetworkControls(); + Int mode = 0; + if (c.proxyMode != nullptr) + { + GadgetComboBoxGetSelectedPos(c.proxyMode, &mode); + } + if (c.proxyAddress != nullptr) + { + if (networkProxyEntryCustom) + { + networkProxyCustomText = networkNarrow(GadgetTextEntryGetText(c.proxyAddress)); + } + networkProxyEntryCustom = mode == 2; + const std::string shown = mode == 2 ? networkProxyCustomText : mode == 1 ? std::string("Direct") : networkSystemProxyText.empty() ? std::string("Checking...") : networkSystemProxyText; + GadgetTextEntrySetText(c.proxyAddress, networkWide(shown)); + c.proxyAddress->winEnable(!networkLocked && mode == 2); + } + Int server = 0; + if (c.server != nullptr) + { + GadgetComboBoxGetSelectedPos(c.server, &server); + } + if (c.serverAddress != nullptr) + { + if (networkServerEntryCustom) + { + networkServerCustomText = networkNarrow(GadgetTextEntryGetText(c.serverAddress)); + } + networkServerEntryCustom = server == 1; + GadgetTextEntrySetText(c.serverAddress, networkWide(server == 1 ? networkServerCustomText : networkServerHost())); + c.serverAddress->winEnable(!networkLocked && server == 1); + } +} + +static void networkPoll() +{ + if (networkSystemProxy == nullptr || !networkSystemProxy->ready.load(std::memory_order_acquire)) + { + return; + } + networkSystemProxyText = networkSystemProxy->result.error.empty() && !networkSystemProxy->result.source.empty() ? networkSystemProxy->result.source : std::string("Unavailable"); + networkSystemProxy.reset(); + networkRefresh(); +} + +// the options layout is not on the shell stack and has no update of its own, so the poll runs from the main thread tasks until the lookup is in +static void networkPollLater() +{ + ServiceConnection::RunOnMainThread([]() + { + networkPoll(); + if (networkSystemProxy != nullptr) + { + networkPollLater(); + } + }); +} + +static void networkShow(const ServiceConnectionSettings &settings) +{ + const NetworkControls c = getNetworkControls(); + const Int proxy = settings.proxyMode == "disabled" ? 1 : settings.proxyMode == "custom" ? 2 : 0; + networkFillCombo(c.proxyMode, { L"Automatic", L"Disabled", L"Custom" }, proxy); + networkProxyCustomText = settings.proxyURL; + if (networkProxyCustomText.compare(0, 7, "http://") == 0) + { + networkProxyCustomText.erase(0, 7); + } + const Int server = settings.serverMode == "custom" ? 1 : 0; + networkFillCombo(c.server, { L"Automatic", L"Custom" }, server); + networkServerCustomText = settings.serverAddress; + if (networkServerCustomText.compare(0, 8, "https://") == 0) + { + networkServerCustomText.erase(0, 8); + } + networkProxyEntryCustom = false; + networkServerEntryCustom = false; + networkRefresh(); +} + +static void networkInit() +{ + networkTestPassed = false; + networkDiscard = false; + const NetworkControls c = getNetworkControls(); + if (c.tabOnline != nullptr) + { + GadgetButtonSetText(c.tabOnline, UnicodeString(L"Online")); + c.tabOnline->winSetTooltip(UnicodeString(L"Settings for playing on Generals Online.")); + } + if (c.tabLan != nullptr) + { + GadgetButtonSetText(c.tabLan, UnicodeString(L"LAN")); + c.tabLan->winSetTooltip(UnicodeString(L"Settings for LAN games.")); + } + networkSetText(c.proxyModeLabel, L"Proxy"); + networkSetText(c.proxyAddressLabel, L"Proxy address"); + networkSetText(c.serverLabel, L"Server"); + networkSetText(c.serverAddressLabel, L"Server address"); + networkSetText(c.lanLabel, L"LAN IP"); + networkSetTooltip(c.proxyMode, L"Automatic follows the Windows proxy settings. Disabled connects directly. Custom uses the proxy address."); + networkSetTooltip(c.proxyAddress, L"Proxy as host:port, or a full http://, https:// or socks5h:// address. Used with Custom."); + networkSetTooltip(c.server, L"Automatic uses the Generals Online servers. Custom uses your own server. Applies at the next login."); + networkSetTooltip(c.serverAddress, L"Your own server as host:port. Only https is allowed. Used with Custom."); + networkSetTooltip(comboBoxLANIP, L"The network adapter used for LAN games."); + networkStartSystemLookup(); + networkPollLater(); + networkShow(NGMP_OnlineServicesManager::Settings.Network_GetConnectionSettings()); + networkShowTab(true); +} + +// the connection settings can't change while a session is open or in a game +static void networkLock() +{ + networkLocked = true; + const NetworkControls c = getNetworkControls(); + for (GameWindow *window : { c.proxyMode, c.proxyAddress, c.server, c.serverAddress }) + { + if (window != nullptr) + { + window->winEnable(FALSE); + } + } +} + +static void networkDefaults() +{ + networkShow(ServiceConnectionSettings()); + if (comboBoxLANIP != nullptr) + { + GadgetComboBoxSetSelectedPos(comboBoxLANIP, 0); + GadgetComboBoxSetText(comboBoxLANIP, L"***.***.***.***"); + } +} + +// the controls as settings; false with the reason when an address is not valid +static bool networkRead(ServiceConnectionSettings &settings, std::string &error) +{ + const NetworkControls c = getNetworkControls(); + Int mode = 0; + if (c.proxyMode != nullptr) + { + GadgetComboBoxGetSelectedPos(c.proxyMode, &mode); + settings.proxyMode = mode == 1 ? "disabled" : mode == 2 ? "custom" : "automatic"; + } + if (c.proxyAddress != nullptr) + { + std::string address = networkTrimmed(networkProxyEntryCustom ? networkNarrow(GadgetTextEntryGetText(c.proxyAddress)) : networkProxyCustomText); + if (!address.empty() && address.find("://") == std::string::npos) + { + address = "http://" + address; + } + settings.proxyURL = address; + if (settings.proxyMode == "custom" && !ServiceConnection::ValidateProxyURL(address, error)) + { + return false; + } + if (settings.proxyMode != "custom" && !ServiceConnection::ValidateProxyURL(address, error)) + { + settings.proxyURL.clear(); + error.clear(); + } + } + Int server = 0; + if (c.server != nullptr) + { + GadgetComboBoxGetSelectedPos(c.server, &server); + settings.serverMode = server == 1 ? "custom" : "automatic"; + } + if (c.serverAddress != nullptr) + { + settings.serverAddress = networkTrimmed(networkServerEntryCustom ? networkNarrow(GadgetTextEntryGetText(c.serverAddress)) : networkServerCustomText); + if (settings.serverMode == "custom") + { + return ServiceConnection::NormalizeCustomEndpoint(settings.serverAddress, settings.serverAddress, error); + } + settings.serverAddress.clear(); + } + return true; +} + +// the proxy or the server differs from the saved one; an address only counts for its Custom choice +static bool networkChanged(const ServiceConnectionSettings &saved, const ServiceConnectionSettings &edited) +{ + if (saved.proxyMode != edited.proxyMode || saved.serverMode != edited.serverMode) + { + return true; + } + if (edited.proxyMode == "custom" && ServiceConnection::CanonicalProxyKey(saved.proxyURL) != ServiceConnection::CanonicalProxyKey(edited.proxyURL)) + { + return true; + } + return edited.serverMode == "custom" && saved.serverAddress != edited.serverAddress; +} + +static void pressAccept() +{ + GameWindow *accept = networkControl("OptionsMenu.wnd:ButtonAccept"); + if (accept != nullptr) + { + TheWindowManager->winSendSystemMsg(accept->winGetParent(), GBM_SELECTED, (WindowMsgData)accept, accept->winGetWindowId()); + } +} + +// Accept with changed connection settings tests them first; true when Accept goes on to save the other options and close +static bool networkAccept() +{ + if (networkDiscard) + { + networkDiscard = false; + return true; + } + const NetworkControls c = getNetworkControls(); + if (networkLocked || (c.proxyMode == nullptr && c.server == nullptr)) + { + return true; + } + const ServiceConnectionSettings saved = NGMP_OnlineServicesManager::Settings.Network_GetConnectionSettings(); + ServiceConnectionSettings edited = saved; + std::string error; + if (!networkRead(edited, error)) + { + MessageBoxOk(UnicodeString(L"Connection settings"), networkWide(error), nullptr); + return false; + } + if (networkChanged(saved, edited) && !networkTestPassed) + { + ServiceConnection::ShowAcceptTest(edited, [](ServiceConnection::AcceptChoice choice) + { + if (choice == ServiceConnection::AcceptChoice::Save) + { + networkTestPassed = true; + } + else if (choice == ServiceConnection::AcceptChoice::Discard) + { + networkDiscard = true; + } + if (choice != ServiceConnection::AcceptChoice::KeepEditing) + { + pressAccept(); + } + }); + return false; + } + networkTestPassed = false; + if (!NGMP_OnlineServicesManager::Settings.Network_SaveConnectionSettings(edited, error)) + { + MessageBoxOk(UnicodeString(L"Connection settings"), networkWide(error), nullptr); + return false; + } + return true; +} + +static bool isNetworkControl(GameWindow *control, const char *name) +{ + return control->winGetWindowId() == TheNameKeyGenerator->nameToKey(name); +} +#endif + static void setDefaults() { constexpr const Bool ModifyDisplaySettings = FALSE; @@ -238,7 +665,10 @@ static void setDefaults() //------------------------------------------------------------------------------------------------- // send Delay - GadgetCheckBoxSetChecked(checkSendDelay, FALSE); + if (checkSendDelay) + { + GadgetCheckBoxSetChecked(checkSendDelay, FALSE); + } if constexpr (ModifyDisplaySettings) { @@ -1414,7 +1844,10 @@ void OptionsMenuInit( WindowLayout *layout, void *userData ) DEBUG_LOG(("Scroll Speed %d", scrollPos)); // set the send delay check box - GadgetCheckBoxSetChecked(checkSendDelay, TheGlobalData->m_firewallSendDelay); + if (checkSendDelay) + { + GadgetCheckBoxSetChecked(checkSendDelay, TheGlobalData->m_firewallSendDelay); + } // set volume sliders @@ -1447,9 +1880,11 @@ void OptionsMenuInit( WindowLayout *layout, void *userData ) if (comboBoxOnlineIP) comboBoxOnlineIP->winEnable(FALSE); - checkSendDelay->winEnable(FALSE); + if (checkSendDelay) + checkSendDelay->winEnable(FALSE); - buttonFirewallRefresh->winEnable(FALSE); + if (buttonFirewallRefresh) + buttonFirewallRefresh->winEnable(FALSE); if (comboBoxDetail) comboBoxDetail->winEnable(FALSE); @@ -1471,6 +1906,13 @@ void OptionsMenuInit( WindowLayout *layout, void *userData ) } +#if defined(GENERALS_ONLINE) + networkInit(); + if ((TheGameLogic->isInGame() && TheGameLogic->getGameMode() != GAME_SHELL) || NGMP_OnlineServicesManager::GetInstance() != nullptr) + { + networkLock(); + } +#endif TheWindowManager->winSetModal(parent); ignoreSelected = FALSE; } @@ -1504,7 +1946,6 @@ void OptionsMenuShutdown( WindowLayout *layout, void *userData ) //------------------------------------------------------------------------------------------------- void OptionsMenuUpdate( WindowLayout *layout, void *userData ) { - } //------------------------------------------------------------------------------------------------- @@ -1625,6 +2066,12 @@ WindowMsgHandledType OptionsMenuSystem( GameWindow *window, UnsignedInt msg, showAdvancedOptions(); } +#if defined(GENERALS_ONLINE) + else if (isNetworkControl(control, "OptionsMenu.wnd:ComboBoxProxyMode") || isNetworkControl(control, "OptionsMenu.wnd:ComboBoxServer")) + { + networkRefresh(); + } +#endif break; } @@ -1657,6 +2104,11 @@ WindowMsgHandledType OptionsMenuSystem( GameWindow *window, UnsignedInt msg, } else if (controlID == buttonAccept ) { +#if defined(GENERALS_ONLINE) + // changed connection settings are tested first; a rejected address keeps the menu open + if (!networkAccept()) + break; +#endif saveOptions(); if (pref) @@ -1688,7 +2140,20 @@ WindowMsgHandledType OptionsMenuSystem( GameWindow *window, UnsignedInt msg, else if (controlID == buttonDefaults ) { setDefaults(); +#if defined(GENERALS_ONLINE) + networkDefaults(); +#endif } +#if defined(GENERALS_ONLINE) + else if (isNetworkControl(control, "OptionsMenu.wnd:ButtonTabOnline")) + { + networkShowTab(true); + } + else if (isNetworkControl(control, "OptionsMenu.wnd:ButtonTabLan")) + { + networkShowTab(false); + } +#endif else if (controlID == ButtonAdvancedAcceptID ) { acceptAdvancedOptions(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp index d588e90cc31..d1d2da5bea0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp @@ -2726,26 +2726,6 @@ GameWindow *GameWindowManager::winCreateFromScript( AsciiString filenameString, else strlcpy(filepath, filename, ARRAY_SIZE(filepath)); -#if defined(GENERALS_ONLINE) - // check Generals Online first - char gofilepath[_MAX_PATH] = "GeneralsOnlineGameData\\"; - if (strchr(filename, '\\') == NULL) - sprintf(gofilepath, "GeneralsOnlineGameData\\%s", filename); - else - strcpy(gofilepath, filename); - - inFile = TheFileSystem->openFile(gofilepath, File::READ); - if (inFile == NULL) - { - // fall back to game archive - inFile = TheFileSystem->openFile(filepath, File::READ); - if (inFile == NULL) - { - DEBUG_LOG(("WinCreateFromScript: Cannot access file '%s'.\n", filename)); - return NULL; - } - } -#else // Open the input file inFile = TheFileSystem->openFile(filepath, File::READ); if (inFile == nullptr) @@ -2753,7 +2733,6 @@ GameWindow *GameWindowManager::winCreateFromScript( AsciiString filenameString, DEBUG_LOG(( "WinCreateFromScript: Cannot access file '%s'.", filename )); return nullptr; } -#endif // read into memory inFile=inFile->convertToRAMFile(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp index 37cf2502362..122e8bbc275 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp @@ -33,6 +33,9 @@ #include "Common/RandomValue.h" #include "GameClient/Shell.h" #include "GameClient/WindowLayout.h" +#include "Common/version.h" +#include "GameClient/GadgetStaticText.h" +#include "GameClient/Image.h" #include "GameClient/GameWindowManager.h" #include "GameClient/GameWindowTransitions.h" #include "GameClient/IMEManager.h" @@ -657,6 +660,67 @@ void Shell::unlinkScreen(WindowLayout* screen) } +//------------------------------------------------------------------------------------------------- +// Generals Online: a screen with a ruler gets the version label (Menus/MainMenuVersion.wnd) as a child of it. It is drawn right on top of the +// ruler, so whatever covers the ruler covers it too, and it is shown and hidden with the ruler. +//------------------------------------------------------------------------------------------------- +static GameWindow *findRuler(GameWindow *window) +{ + for (; window != nullptr; window = window->winGetNext()) + { + const Image *image = window->winGetEnabledImage(0); + if (image != nullptr && image->getName().compareNoCase("MainMenuRuler") == 0) + { + return window; + } + if (GameWindow *ruler = findRuler(window->winGetChild())) + { + return ruler; + } + } + return nullptr; +} + +static void addVersionLabel(WindowLayout *screen) +{ + // the ruler is the window with the ruler image, whatever the screen calls it + GameWindow *ruler = nullptr; + for (GameWindow *top = screen->getFirstWindow(); top != nullptr && ruler == nullptr; top = top->winGetNextInLayout()) + { + const Image *image = top->winGetEnabledImage(0); + if (image != nullptr && image->getName().compareNoCase("MainMenuRuler") == 0) + { + ruler = top; + } + else + { + ruler = findRuler(top->winGetChild()); + } + } + if (ruler == nullptr) + { + return; + } + + GameWindow *label = TheWindowManager->winCreateFromScript(AsciiString("Menus/MainMenuVersion.wnd")); + if (label == nullptr) + { + return; + } + + // the child position is relative to the ruler + ICoord2D labelPos; + ICoord2D rulerPos; + label->winGetScreenPosition(&labelPos.x, &labelPos.y); + ruler->winGetScreenPosition(&rulerPos.x, &rulerPos.y); + label->winSetParent(ruler); + label->winSetPosition(labelPos.x - rulerPos.x, labelPos.y - rulerPos.y); + if (TheVersion != nullptr) + { + GadgetStaticTextSetText(label, TheVersion->getUnicodeProductVersionHashString()); + } +} + //------------------------------------------------------------------------------------------------- /** Actually do the work for a push */ //------------------------------------------------------------------------------------------------- @@ -669,6 +733,10 @@ void Shell::doPush(AsciiString layoutFile) // create new layout and load from window manager newScreen = TheWindowManager->winCreateLayout(layoutFile); DEBUG_ASSERTCRASH(newScreen != NULL, ("Shell unable to load pending push layout")); + if (newScreen != nullptr) + { + addVersionLabel(newScreen); + } // link screen to the top linkScreen(newScreen); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index b833f4c59d1..c4d7401906f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -7135,27 +7135,7 @@ void InGameUI::drawSystemTime(Int& x, Int& y) UnicodeString TimeString; -#if defined(GENERALS_ONLINE) - if (NGMP_OnlineServicesManager::Settings.Graphics_DrawStatsOverlay() && TheNetwork != nullptr) - { - int64_t currTime = std::chrono::duration_cast(std::chrono::utc_clock::now().time_since_epoch()).count(); - if (currTime - lastFPSUpdate >= 1000) - { - lastFPSUpdate = currTime; - m_lastFPS = m_currentFPS; - m_currentFPS = 0; - } - ++m_currentFPS; - - TimeString.format(L"%2.2d:%2.2d:%2.2d", systemTime.wHour, systemTime.wMinute, systemTime.wSecond); - } - else - { - TimeString.format(L"%2.2d:%2.2d:%2.2d", systemTime.wHour, systemTime.wMinute, systemTime.wSecond); - } -#else TimeString.format(L"%2.2d:%2.2d:%2.2d", systemTime.wHour, systemTime.wMinute, systemTime.wSecond); -#endif m_systemTimeString->setText(TimeString); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/CredentialStore.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/CredentialStore.cpp new file mode 100644 index 00000000000..b06e4384558 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/CredentialStore.cpp @@ -0,0 +1,301 @@ +#include "GameNetwork/GeneralsOnline/CredentialStore.h" +#include "GameNetwork/GeneralsOnline/json.hpp" + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "Crypt32.lib") + +namespace ServiceConnection +{ +namespace +{ +using Json = nlohmann::json; + +std::mutex storeMutex; +std::string (*pathProvider)() = nullptr; +void (*logSink)(const std::string&) = nullptr; +std::atomic forcedCryptError{0}; +// entries that could not be written; they win over the file and use the same layout +Json overlay = Json::object(); +bool fileUnreadable = false; + +void Log(const std::string& line) +{ + static std::string previous; + if (previous == line) + return; + previous = line; + if (logSink) + logSink(line); +} + +std::string Describe(const char* operation, unsigned long code) +{ + return std::string("[Connection] Credentials ") + operation + " failed (" + std::to_string(code) + "), kept for this session only"; +} + +bool Protect(const std::string& plain, std::string& blob, unsigned long& error) +{ + if (const unsigned long forced = forcedCryptError.load()) + { + error = forced; + return false; + } + DATA_BLOB input{ (DWORD)plain.size(), (BYTE*)plain.data() }; + DATA_BLOB output{}; + if (!CryptProtectData(&input, L"GO Credentials", nullptr, nullptr, nullptr, 0, &output)) + { + error = GetLastError(); + return false; + } + blob.assign((const char*)output.pbData, output.cbData); + LocalFree(output.pbData); + return true; +} + +bool Unprotect(const std::string& blob, std::string& plain, unsigned long& error) +{ + if (const unsigned long forced = forcedCryptError.load()) + { + error = forced; + return false; + } + DATA_BLOB input{ (DWORD)blob.size(), (BYTE*)blob.data() }; + DATA_BLOB output{}; + if (!CryptUnprotectData(&input, nullptr, nullptr, nullptr, nullptr, 0, &output)) + { + error = GetLastError(); + return false; + } + plain.assign((const char*)output.pbData, output.cbData); + SecureZeroMemory(output.pbData, output.cbData); + LocalFree(output.pbData); + return true; +} + +// the file of current builds holds {"refresh_token": ...}; it becomes the token of the default GO route +void Normalize(Json& doc) +{ + if (!doc.is_object()) + doc = Json::object(); + if (!doc.contains("tokens") || !doc["tokens"].is_object()) + doc["tokens"] = Json::object(); + if (!doc.contains("proxies") || !doc["proxies"].is_object()) + doc["proxies"] = Json::object(); + if (doc.contains("refresh_token")) + { + if (doc["refresh_token"].is_string() && !doc["refresh_token"].get().empty() && !doc["tokens"].contains("go")) + doc["tokens"]["go"] = doc["refresh_token"]; + doc.erase("refresh_token"); + } +} + +// the stored document; a file that cannot be read counts as empty and is never overwritten +Json ReadFile() +{ + fileUnreadable = false; + Json doc = Json::object(); + if (!pathProvider) + return doc; + const std::string path = pathProvider(); + std::string bytes; + if (FILE* file = fopen(path.c_str(), "rb")) + { + fseek(file, 0, SEEK_END); + const long size = ftell(file); + fseek(file, 0, SEEK_SET); + if (size > 0) + { + bytes.resize(size); + if (fread(bytes.data(), 1, size, file) != (size_t)size) + bytes.clear(); + } + fclose(file); + } + if (bytes.empty()) + return doc; + // a file that was never encrypted still loads + Json parsed = Json::parse(bytes, nullptr, false); + if (!parsed.is_discarded() && parsed.is_object()) + return parsed; + std::string plain; + unsigned long error = 0; + if (Unprotect(bytes, plain, error)) + { + parsed = Json::parse(plain, nullptr, false); + if (!plain.empty()) + SecureZeroMemory(plain.data(), plain.size()); + if (!parsed.is_discarded() && parsed.is_object()) + return parsed; + error = ERROR_INVALID_DATA; + } + fileUnreadable = true; + Log(Describe("read", error)); + return doc; +} + +Json Merged() +{ + Json doc = ReadFile(); + Normalize(doc); + Json over = overlay; + Normalize(over); + for (const char* section : { "tokens", "proxies" }) + { + for (auto it = over[section].begin(); it != over[section].end(); ++it) + doc[section][it.key()] = it.value(); + } + return doc; +} + +bool WriteFile(const Json& doc) +{ + if (!pathProvider || fileUnreadable) + return false; + std::string plain; + try + { + plain = doc.dump(); + } + catch (...) + { + Log(Describe("write", ERROR_INVALID_DATA)); + return false; + } + std::string blob; + unsigned long error = 0; + const bool encrypted = Protect(plain, blob, error); + if (!plain.empty()) + SecureZeroMemory(plain.data(), plain.size()); + if (!encrypted) + { + Log(Describe("encrypt", error)); + return false; + } + const std::string path = pathProvider(); + const std::string temp = path + ".tmp"; + bool saved = false; + if (FILE* file = fopen(temp.c_str(), "wb")) + { + saved = fwrite(blob.data(), 1, blob.size(), file) == blob.size(); + if (fclose(file) != 0) + saved = false; + } + if (saved) + saved = MoveFileExA(temp.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0; + if (!saved) + { + Log(Describe("write", GetLastError())); + DeleteFileA(temp.c_str()); + } + return saved; +} + +// changes one entry and rewrites the whole file; when that fails the entry stays in the overlay for this session +bool Change(const char* section, const std::string& key, const Json* value) +{ + std::lock_guard lock(storeMutex); + Json doc = Merged(); + if (value) + doc[section][key] = *value; + else + doc[section].erase(key); + if (WriteFile(doc)) + { + overlay = Json::object(); + return true; + } + Normalize(overlay); + if (value) + overlay[section][key] = *value; + else + overlay[section].erase(key); + return false; +} + +bool Find(const char* section, const std::string& key, Json& value) +{ + std::lock_guard lock(storeMutex); + const Json doc = Merged(); + const auto it = doc[section].find(key); + if (it == doc[section].end()) + return false; + value = *it; + return true; +} +} + +void ConfigureCredentialStore(std::string (*provider)()) +{ + std::lock_guard lock(storeMutex); + pathProvider = provider; +} + +void SetCredentialLogSink(void (*sink)(const std::string&)) +{ + std::lock_guard lock(storeMutex); + logSink = sink; +} + +void SetCredentialCryptFailureForTest(unsigned long errorCode) +{ + forcedCryptError.store(errorCode); +} + +bool SaveProxyLogin(const std::string& proxyKey, const std::string& user, const std::string& password) +{ + if (proxyKey.empty()) + return false; + const Json value = { { "user", user }, { "password", password } }; + return Change("proxies", proxyKey, &value); +} + +bool LoadProxyLogin(const std::string& proxyKey, std::string& user, std::string& password) +{ + Json value; + if (proxyKey.empty() || !Find("proxies", proxyKey, value) || !value.is_object() || !value.contains("user") || !value["user"].is_string()) + return false; + user = value["user"].get(); + password = value.contains("password") && value["password"].is_string() ? value["password"].get() : std::string(); + return !user.empty(); +} + +bool DeleteProxyLogin(const std::string& proxyKey) +{ + Json value; + if (proxyKey.empty() || !Find("proxies", proxyKey, value)) + return true; + return Change("proxies", proxyKey, nullptr); +} + +bool SaveRefreshToken(const std::string& serverKey, const std::string& token) +{ + if (serverKey.empty() || token.empty()) + return false; + const Json value = token; + return Change("tokens", serverKey, &value); +} + +bool LoadRefreshToken(const std::string& serverKey, std::string& token) +{ + Json value; + if (serverKey.empty() || !Find("tokens", serverKey, value) || !value.is_string()) + return false; + token = value.get(); + return !token.empty(); +} + +bool DeleteRefreshToken(const std::string& serverKey) +{ + Json value; + if (serverKey.empty() || !Find("tokens", serverKey, value)) + return true; + return Change("tokens", serverKey, nullptr); +} +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.cpp index 347c65de981..56b673967c5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.cpp @@ -1,17 +1,22 @@ #include "GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h" +#include "Common/ArchiveFileSystem.h" #include "../json.hpp" #include "../OnlineServices_LobbyInterface.h" #include "../OnlineServices_Init.h" +#include +#include "GameNetwork/GeneralsOnline/CredentialStore.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_Auth.h" +#include "Common/OptionPreferences.h" #define SETTINGS_KEY_CAMERA "camera" #define SETTINGS_KEY_CAMERA_MIN_HEIGHT "min_height" -#define SETTINGS_KEY_CAMERA_MOVE_SPEED_RATIO "move_speed_ratio" +#define SETTINGS_KEY_CAMERA_LEGACY_MOVE_SPEED_RATIO "move_speed_ratio" #define SETTINGS_KEY_CAMERA_MAX_HEIGHT_WHEN_LOBBY_HOST "max_height_only_when_lobby_host" #define SETTINGS_KEY_RENDER "render" #define SETTINGS_KEY_RENDER_LIMIT_FRAMERATE "limit_framerate" #define SETTINGS_KEY_RENDER_FRAMERATE_LIMIT_FPS_VAL "fps_limit" -#define SETTINGS_KEY_RENDER_DRAW_STATS_OVERLAY "stats_overlay" +#define SETTINGS_KEY_RENDER_LEGACY_DRAW_STATS_OVERLAY "stats_overlay" #define SETTINGS_KEY_CHAT "chat" #define SETTINGS_KEY_CHAT_LIFE_SECONDS "duration_seconds_until_fade_out" @@ -30,14 +35,29 @@ #define SETTINGS_KEY_DEBUG_VERBOSE_LOGGING "verbose_logging" #define SETTINGS_KEY_NETWORK "network" -#define SETTINGS_KEY_NETWORK_HTTP_VERSION "http_version" +#define SETTINGS_KEY_NETWORK_LEGACY_HTTP_VERSION "http_version" #define SETTINGS_KEY_NETWORK_USE_ALTERNATIVE_ENDPOINT "use_alternative_endpoint" +#define SETTINGS_KEY_NETWORK_SERVER "server" +#define SETTINGS_KEY_NETWORK_SERVER_MODE "mode" +#define SETTINGS_KEY_NETWORK_SERVER_ADDRESS "address" +#define SETTINGS_KEY_NETWORK_PROXY "proxy" +#define SETTINGS_KEY_NETWORK_PROXY_MODE "mode" +#define SETTINGS_KEY_NETWORK_PROXY_ADDRESS "address" +#define SETTINGS_KEY_NETWORK_PROXY_WINDOWS_LOGIN "windows_login" +#define SETTINGS_KEY_NETWORK_LAST_ROUTE "last_route" +#define LEGACY_ALTERNATIVE_ROUTE_HOST "api-ru.playgenerals.online" #define SETTINGS_KEY_PLUGINS "plugins" #define SETTINGS_KEY_PLUGINS_ANTICHEAT "anticheat" #define SETTINGS_KEY_DATAPACKS "data_packs" -#define SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH "use_community_data_patch" +#define SETTINGS_KEY_DATAPACKS_USER "user" +#define SETTINGS_KEY_DATAPACKS_GENERALS_ONLINE "generals_online" +#define SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH "community_patch" +#define SETTINGS_KEY_DATAPACKS_ENABLED "enabled" +#define SETTINGS_KEY_DATAPACKS_INCLUDE_PATHS "include_paths" +#define SETTINGS_KEY_DATAPACKS_EXCLUDE_PATHS "exclude_paths" +#define SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH_LEGACY "use_community_data_patch" // before the layers, read once and dropped on save #define SETTINGS_FILENAME_LEGACY "GeneralsOnline_settings.json" #define SETTINGS_FILENAME "settings.json" @@ -94,8 +114,112 @@ std::string GenOnlineSettings::GetAnticheatPlugin() const return m_Plugins_Anticheat; } +// Reads a list of paths of data_packs, an entry that is not valid is dropped. Returns FALSE and keeps the list when the field is missing or not a list. +static bool LoadDataPackPaths(const nlohmann::json& jsonLayer, const char* layerName, const char* key, bool excludes, std::vector& paths) +{ + if (!jsonLayer.contains(key)) + { + return false; + } + + if (!jsonLayer[key].is_array()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Data pack layer %s: %s is not a list, using the default", layerName, key); + return false; + } + + paths.clear(); + for (const auto& path : jsonLayer[key]) + { + AsciiString reason; + bool bValid = false; + if (path.is_string()) + { + bValid = excludes ? PathExcludeList::parse(path.get(), nullptr, &reason) : ArchiveFileSystem::parseMountEntry(AsciiString(path.get().c_str()), nullptr, &reason); + } + + if (bValid) + { + paths.push_back(path.get()); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Data pack layer %s: %s entry dropped from the settings (%s)", layerName, key, path.is_string() ? reason.str() : "not a string"); + } + } + return true; +} + +// Reads one layer of data_packs. A missing or invalid field keeps what the layer holds, its default. excludePaths is only read for the user layer. +static void LoadDataPackLayer(const nlohmann::json& dataPacks, const char* name, GenOnlineDataPackLayer& layer, std::vector* excludePaths) +{ + if (!dataPacks.contains(name)) + { + return; + } + + const nlohmann::json& jsonLayer = dataPacks[name]; + if (!jsonLayer.is_object()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Data pack layer %s in the settings is not an object, using the default", name); + return; + } + + if (jsonLayer.contains(SETTINGS_KEY_DATAPACKS_ENABLED)) + { + if (jsonLayer[SETTINGS_KEY_DATAPACKS_ENABLED].is_boolean()) + { + layer.m_enabled = jsonLayer[SETTINGS_KEY_DATAPACKS_ENABLED].get(); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Data pack layer %s: enabled is not a boolean, using the default", name); + } + } + + LoadDataPackPaths(jsonLayer, name, SETTINGS_KEY_DATAPACKS_INCLUDE_PATHS, false, layer.m_includePaths); + + if (excludePaths != nullptr) + { + LoadDataPackPaths(jsonLayer, name, SETTINGS_KEY_DATAPACKS_EXCLUDE_PATHS, true, *excludePaths); + } +} + +static nlohmann::json DataPackLayerToJson(const GenOnlineDataPackLayer& layer, const std::vector* excludePaths) +{ + nlohmann::json jsonLayer = nlohmann::json::object(); + jsonLayer[SETTINGS_KEY_DATAPACKS_ENABLED] = layer.m_enabled; + jsonLayer[SETTINGS_KEY_DATAPACKS_INCLUDE_PATHS] = layer.m_includePaths; + if (excludePaths != nullptr) + { + jsonLayer[SETTINGS_KEY_DATAPACKS_EXCLUDE_PATHS] = *excludePaths; + } + return jsonLayer; +} + +// The old right mouse scroll speed was ratio * Options.ini scroll speed * (screen width / 2) pixels. The scroll now measures the drag at 800x600 +// and uses the 1.6 horizontal speed factor of GameData.ini, so the same speed needs a scroll speed of +// old scroll speed * ratio * (width / 2) / (1.6 * 400), within the range of the options slider. +static void MigrateMoveSpeedRatioToOptions(float ratio) +{ + OptionPreferences options; + Int width = 0; + Int height = 0; + options.getResolution(&width, &height); + const auto scrollFactor = options.find("ScrollFactor"); + const Int oldSlider = scrollFactor == options.end() ? 50 : (std::max)(1, atoi(scrollFactor->second.str())); + const Int newSlider = (std::max)(1, static_cast(oldSlider * ratio * (width / 2.0f) / (1.6f * 400.0f) + 0.5f)); + AsciiString text; + text.format("%d", newSlider); + options["ScrollFactor"] = text; + options.write(); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[Settings] Moved camera.move_speed_ratio=%.2f to Options.ini ScrollFactor: %d -> %d at %dx%d", ratio, oldSlider, newSlider, width, height); +} + void GenOnlineSettings::Load(void) { + ServiceConnection::ConfigureCredentialStore(&NGMP_OnlineServices_AuthInterface::GetCredentialsFilePath); + ServiceConnection::SetCredentialLogSink([](const std::string& line) { NetworkLog(ELogVerbosity::LOG_RELEASE, "%s", line.c_str()); }); char GameDir[MAX_PATH + 1] = {}; ::GetCurrentDirectoryA(MAX_PATH + 1u, GameDir); std::string strSettingsFileDir = std::format("{}/GeneralsOnlineData/", TheGlobalData->getPath_UserData().str()); @@ -163,6 +287,9 @@ void GenOnlineSettings::Load(void) try { jsonSettings = nlohmann::json::parse(strJSON); + if (!jsonSettings.is_object()) + throw std::runtime_error("Settings must be an object"); + m_PreservedJSON = jsonSettings.dump(); } catch (...) @@ -184,9 +311,10 @@ void GenOnlineSettings::Load(void) m_Camera_MinHeight = std::max(static_cast(cameraSettings[SETTINGS_KEY_CAMERA_MIN_HEIGHT]), m_Camera_MinHeight_default); } - if (cameraSettings.contains(SETTINGS_KEY_CAMERA_MOVE_SPEED_RATIO)) + // the right mouse scroll speed is the Options.ini scroll speed now, an old ratio is converted once + if (cameraSettings.contains(SETTINGS_KEY_CAMERA_LEGACY_MOVE_SPEED_RATIO) && cameraSettings[SETTINGS_KEY_CAMERA_LEGACY_MOVE_SPEED_RATIO].is_number()) { - m_Camera_MoveSpeedRatio = std::clamp(static_cast(cameraSettings[SETTINGS_KEY_CAMERA_MOVE_SPEED_RATIO]), 0.05f, 25.f); + MigrateMoveSpeedRatioToOptions(std::clamp(static_cast(cameraSettings[SETTINGS_KEY_CAMERA_LEGACY_MOVE_SPEED_RATIO]), 0.05f, 25.f)); } if (cameraSettings.contains(SETTINGS_KEY_CAMERA_MAX_HEIGHT_WHEN_LOBBY_HOST)) @@ -208,35 +336,126 @@ void GenOnlineSettings::Load(void) { m_Render_FramerateLimit_FPSVal = renderSettings[SETTINGS_KEY_RENDER_FRAMERATE_LIMIT_FPS_VAL]; } - - if (renderSettings.contains(SETTINGS_KEY_RENDER_DRAW_STATS_OVERLAY)) - { - m_Render_DrawStatsOverlay = renderSettings[SETTINGS_KEY_RENDER_DRAW_STATS_OVERLAY]; - } } if (jsonSettings.contains(SETTINGS_KEY_NETWORK)) { - auto networkSettings = jsonSettings[SETTINGS_KEY_NETWORK]; - - if (networkSettings.contains(SETTINGS_KEY_NETWORK_HTTP_VERSION)) + // Any network value that is unknown, malformed or of the wrong type goes back to its default and is logged, one line per field. + // The load ends with a save, so the file never keeps the invalid value. Addresses can hold a secret, so their old value is not logged. + auto resetField = [](const std::string& path, const nlohmann::json& old, bool secret) + { + const std::string oldText = secret ? std::string("hidden") : old.is_string() ? old.get().substr(0, 64) : std::string(old.type_name()); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[Settings] %s is not valid (%s), reset to its default", path.c_str(), oldText.c_str()); + }; + auto choice = [&](const nlohmann::json& section, const std::string& path, const char* key, std::string& value, std::initializer_list allowed) + { + if (!section.contains(key)) + { + return; + } + const auto& found = section[key]; + if (found.is_string() && std::find_if(allowed.begin(), allowed.end(), [&](const char* name) { return found.get() == name; }) != allowed.end()) + { + value = found.get(); + return; + } + resetField(path + "." + key, found, false); + }; + // a section that is present but not an object goes back to its defaults + auto section = [&](const nlohmann::json& parent, const std::string& path, const char* key) -> const nlohmann::json* + { + if (!parent.contains(key)) + { + return nullptr; + } + if (!parent[key].is_object()) + { + resetField(path + "." + key, parent[key], false); + return nullptr; + } + return &parent[key]; + }; + + if (!jsonSettings[SETTINGS_KEY_NETWORK].is_object()) { - int httpVersion = networkSettings[SETTINGS_KEY_NETWORK_HTTP_VERSION]; - - // clamp - if (httpVersion < 0 || httpVersion > HTTP_VERSION_3_0) - { - m_Network_HTTPVersion = EHTTPVersion::HTTP_VERSION_AUTO; - } - else - { - m_Network_HTTPVersion = (EHTTPVersion)httpVersion; - } + resetField(SETTINGS_KEY_NETWORK, jsonSettings[SETTINGS_KEY_NETWORK], false); } - - if (networkSettings.contains(SETTINGS_KEY_NETWORK_USE_ALTERNATIVE_ENDPOINT)) + else { - m_Network_UseAlternativeEndpoint = networkSettings[SETTINGS_KEY_NETWORK_USE_ALTERNATIVE_ENDPOINT]; + const auto& networkSettings = jsonSettings[SETTINGS_KEY_NETWORK]; + + if (const auto* serverSettings = section(networkSettings, SETTINGS_KEY_NETWORK, SETTINGS_KEY_NETWORK_SERVER)) + { + const std::string path = std::string(SETTINGS_KEY_NETWORK) + "." + SETTINGS_KEY_NETWORK_SERVER; + choice(*serverSettings, path, SETTINGS_KEY_NETWORK_SERVER_MODE, m_ConnectionSettings.serverMode, { "automatic", "custom" }); + if (serverSettings->contains(SETTINGS_KEY_NETWORK_SERVER_ADDRESS)) + { + const auto& found = (*serverSettings)[SETTINGS_KEY_NETWORK_SERVER_ADDRESS]; + std::string endpointError; + if (found.is_string() && (found.get().empty() || + ServiceConnection::NormalizeCustomEndpoint(found.get(), m_ConnectionSettings.serverAddress, endpointError))) + { + // an empty address stays empty + } + else + { + resetField(path + "." + SETTINGS_KEY_NETWORK_SERVER_ADDRESS, found, true); + m_ConnectionSettings.serverAddress.clear(); + } + } + } + + if (const auto* proxySettings = section(networkSettings, SETTINGS_KEY_NETWORK, SETTINGS_KEY_NETWORK_PROXY)) + { + const std::string path = std::string(SETTINGS_KEY_NETWORK) + "." + SETTINGS_KEY_NETWORK_PROXY; + choice(*proxySettings, path, SETTINGS_KEY_NETWORK_PROXY_MODE, m_ConnectionSettings.proxyMode, { "automatic", "disabled", "custom" }); + if (proxySettings->contains(SETTINGS_KEY_NETWORK_PROXY_ADDRESS)) + { + const auto& found = (*proxySettings)[SETTINGS_KEY_NETWORK_PROXY_ADDRESS]; + std::string proxyError; + if (found.is_string() && (found.get().empty() || ServiceConnection::ValidateProxyURL(found.get(), proxyError))) + { + m_ConnectionSettings.proxyURL = found.get(); + } + else + { + resetField(path + "." + SETTINGS_KEY_NETWORK_PROXY_ADDRESS, found, true); + } + } + if (proxySettings->contains(SETTINGS_KEY_NETWORK_PROXY_WINDOWS_LOGIN)) + { + const auto& found = (*proxySettings)[SETTINGS_KEY_NETWORK_PROXY_WINDOWS_LOGIN]; + if (found.is_boolean()) + { + m_ConnectionSettings.windowsLogin = found.get(); + } + else + { + resetField(path + "." + SETTINGS_KEY_NETWORK_PROXY_WINDOWS_LOGIN, found, false); + } + } + } + + if (networkSettings.contains(SETTINGS_KEY_NETWORK_LAST_ROUTE)) + { + const auto& found = networkSettings[SETTINGS_KEY_NETWORK_LAST_ROUTE]; + if (found.is_string() && found.get().size() <= 253 && + found.get().find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-") == std::string::npos) + { + m_LastRoute = found.get(); + } + else + { + resetField(std::string(SETTINGS_KEY_NETWORK) + "." + SETTINGS_KEY_NETWORK_LAST_ROUTE, found, false); + } + } + else if (networkSettings.contains(SETTINGS_KEY_NETWORK_USE_ALTERNATIVE_ENDPOINT) && + networkSettings[SETTINGS_KEY_NETWORK_USE_ALTERNATIVE_ENDPOINT].is_boolean() && + networkSettings[SETTINGS_KEY_NETWORK_USE_ALTERNATIVE_ENDPOINT].get()) + { + // the old switch of the released client now only prefers that route + m_LastRoute = LEGACY_ALTERNATIVE_ROUTE_HOST; + } } } @@ -317,11 +536,18 @@ void GenOnlineSettings::Load(void) if (jsonSettings.contains(SETTINGS_KEY_DATAPACKS)) { - auto datapackSettings = jsonSettings[SETTINGS_KEY_DATAPACKS]; + const nlohmann::json& datapackSettings = jsonSettings[SETTINGS_KEY_DATAPACKS]; + + LoadDataPackLayer(datapackSettings, SETTINGS_KEY_DATAPACKS_USER, m_DataPacks_User, &m_DataPacks_UserExcludePaths); + LoadDataPackLayer(datapackSettings, SETTINGS_KEY_DATAPACKS_GENERALS_ONLINE, m_DataPacks_GeneralsOnline, nullptr); + LoadDataPackLayer(datapackSettings, SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH, m_DataPacks_CommunityPatch, nullptr); - if (datapackSettings.contains(SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH)) + // a file from before the layers: the old community patch switch becomes community_patch.enabled + if (!datapackSettings.contains(SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH) + && datapackSettings.contains(SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH_LEGACY) + && datapackSettings[SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH_LEGACY].is_boolean()) { - m_DataPacks_UseCommunityPatch = datapackSettings[SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH]; + m_DataPacks_CommunityPatch.m_enabled = datapackSettings[SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH_LEGACY].get(); } } } @@ -345,7 +571,6 @@ void GenOnlineSettings::Load(void) m_bVerbose = false; m_Render_LimitFramerate = true; m_Render_FramerateLimit_FPSVal = 60; - m_Render_DrawStatsOverlay = true; m_Chat_LifeSeconds = 30; m_Social_Notification_FriendComesOnline_Menus = true; @@ -356,6 +581,7 @@ void GenOnlineSettings::Load(void) if (bReadFailed) { + m_CanSave = false; // keep the file on disk, the defaults above only apply to this session NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Could not read settings file, keeping it untouched"); return; @@ -365,26 +591,36 @@ void GenOnlineSettings::Load(void) { // keep the unparseable file for recovery before the defaults replace it std::filesystem::rename(strSettingsFilePath, strSettingsFilePath + ".bad", ec); + if (ec) + { + m_CanSave = false; + return; + } + m_PreservedJSON = "{}"; } // Always save so we re-serialize anything new or missing Save(); } -void GenOnlineSettings::Save() +bool GenOnlineSettings::Save() { if (!m_bInitialized) { Initialize(); } + if (!m_CanSave) + { + return false; + } + nlohmann::json root = { { SETTINGS_KEY_CAMERA, { {SETTINGS_KEY_CAMERA_MIN_HEIGHT, m_Camera_MinHeight}, {SETTINGS_KEY_CAMERA_MAX_HEIGHT_WHEN_LOBBY_HOST, m_Camera_MaxHeight_LobbyHost}, - { SETTINGS_KEY_CAMERA_MOVE_SPEED_RATIO, m_Camera_MoveSpeedRatio }, } }, @@ -393,15 +629,20 @@ void GenOnlineSettings::Save() { {SETTINGS_KEY_RENDER_LIMIT_FRAMERATE, m_Render_LimitFramerate}, {SETTINGS_KEY_RENDER_FRAMERATE_LIMIT_FPS_VAL, m_Render_FramerateLimit_FPSVal}, - {SETTINGS_KEY_RENDER_DRAW_STATS_OVERLAY, m_Render_DrawStatsOverlay}, } }, { SETTINGS_KEY_NETWORK, { - {SETTINGS_KEY_NETWORK_HTTP_VERSION, m_Network_HTTPVersion}, - {SETTINGS_KEY_NETWORK_USE_ALTERNATIVE_ENDPOINT, m_Network_UseAlternativeEndpoint} + {SETTINGS_KEY_NETWORK_SERVER, { + {SETTINGS_KEY_NETWORK_SERVER_MODE, m_ConnectionSettings.serverMode}, + {SETTINGS_KEY_NETWORK_SERVER_ADDRESS, m_ConnectionSettings.serverAddress}}}, + {SETTINGS_KEY_NETWORK_PROXY, { + {SETTINGS_KEY_NETWORK_PROXY_MODE, m_ConnectionSettings.proxyMode}, + {SETTINGS_KEY_NETWORK_PROXY_ADDRESS, m_ConnectionSettings.proxyURL}, + {SETTINGS_KEY_NETWORK_PROXY_WINDOWS_LOGIN, m_ConnectionSettings.windowsLogin}}}, + {SETTINGS_KEY_NETWORK_LAST_ROUTE, m_LastRoute} } }, @@ -443,12 +684,22 @@ void GenOnlineSettings::Save() { SETTINGS_KEY_DATAPACKS, { - {SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH, m_DataPacks_UseCommunityPatch} + {SETTINGS_KEY_DATAPACKS_USER, DataPackLayerToJson(m_DataPacks_User, &m_DataPacks_UserExcludePaths)}, + {SETTINGS_KEY_DATAPACKS_GENERALS_ONLINE, DataPackLayerToJson(m_DataPacks_GeneralsOnline, nullptr)}, + {SETTINGS_KEY_DATAPACKS_COMMUNITY_PATCH, DataPackLayerToJson(m_DataPacks_CommunityPatch, nullptr)} } } }; - std::string strData = root.dump(1); + auto preserved = nlohmann::json::parse(m_PreservedJSON); + preserved.merge_patch(root); + preserved[SETTINGS_KEY_DATAPACKS] = root[SETTINGS_KEY_DATAPACKS]; // the client owns data_packs completely, stale keys go + // settings this build no longer has are dropped from the file + preserved[SETTINGS_KEY_RENDER].erase(SETTINGS_KEY_RENDER_LEGACY_DRAW_STATS_OVERLAY); + preserved[SETTINGS_KEY_CAMERA].erase(SETTINGS_KEY_CAMERA_LEGACY_MOVE_SPEED_RATIO); + preserved[SETTINGS_KEY_NETWORK].erase(SETTINGS_KEY_NETWORK_USE_ALTERNATIVE_ENDPOINT); + preserved[SETTINGS_KEY_NETWORK].erase(SETTINGS_KEY_NETWORK_LEGACY_HTTP_VERSION); + std::string strData = preserved.dump(1); std::string strSettingsFilePath = std::format("{}/GeneralsOnlineData/{}", TheGlobalData->getPath_UserData().str(), SETTINGS_FILENAME); @@ -472,4 +723,102 @@ void GenOnlineSettings::Save() std::error_code ec; std::filesystem::remove(strTempPath, ec); } + else + { + m_PreservedJSON = strData; + } + return bSaved; +} + +bool GenOnlineSettings::Network_SaveConnectionSettings(const ServiceConnectionSettings& requested, std::string& error) +{ + error.clear(); + ServiceConnectionSettings settings = requested; + if ((settings.serverMode != "automatic" && settings.serverMode != "custom") || + (settings.proxyMode != "automatic" && settings.proxyMode != "disabled" && settings.proxyMode != "custom")) + { + error = "Choose a valid service and proxy mode."; + return false; + } + if (settings.serverMode == "custom") + { + if (!ServiceConnection::NormalizeCustomEndpoint(settings.serverAddress, settings.serverAddress, error)) + { + return false; + } + } + else + { + settings.serverAddress.clear(); + } + if ((settings.proxyMode == "custom" || !settings.proxyURL.empty()) && !ServiceConnection::ValidateProxyURL(settings.proxyURL, error)) + { + return false; + } + if (!m_bInitialized) + { + Initialize(); + } + std::lock_guard lock(m_NetworkMutex); + const auto previous = m_ConnectionSettings; + m_ConnectionSettings = settings; + if (Save()) + { + // a changed proxy starts without anything typed for the old one + const std::string previousProxy = previous.proxyMode == "custom" ? ServiceConnection::CanonicalProxyKey(previous.proxyURL) : std::string(); + const std::string proxy = settings.proxyMode == "custom" ? ServiceConnection::CanonicalProxyKey(settings.proxyURL) : std::string(); + if (previousProxy != proxy) + ServiceConnection::ClearSessionProxyCredentialsExcept(proxy); + ServiceConnection::InvalidateProxyPolicyCache(); + return true; + } + m_ConnectionSettings = previous; + error = "Could not save connection settings. Check the game user-data folder permissions."; + return false; +} + +bool GenOnlineSettings::Network_SaveWindowsLogin(bool windowsLogin) +{ + if (!m_bInitialized) + { + Initialize(); + } + std::lock_guard lock(m_NetworkMutex); + if (m_ConnectionSettings.windowsLogin == windowsLogin) + { + return true; + } + m_ConnectionSettings.windowsLogin = windowsLogin; + if (!Save()) + { + m_ConnectionSettings.windowsLogin = !windowsLogin; + return false; + } + ServiceConnection::InvalidateProxyPolicyCache(); + return true; +} + +bool GenOnlineSettings::Network_SaveLastRoute(const std::string& host) +{ + if (host.empty() || host.size() > 253 || host.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-") != std::string::npos) + { + return false; + } + if (!m_bInitialized) + { + Initialize(); + } + std::lock_guard lock(m_NetworkMutex); + if (m_LastRoute == host) + { + return true; + } + const auto previous = m_LastRoute; + m_LastRoute = host; + if (Save()) + { + return true; + } + m_LastRoute = previous; + return false; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp index a420a04cda6..4094ebeb5b3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp @@ -43,13 +43,14 @@ void HTTPManager::SendPUTRequest(const char* szURI, EIPProtocolVersion protover, } -void HTTPManager::SendS3PUTRequest(const char* szURI, EIPProtocolVersion protover, std::map& inHeaders, std::vector vecBuffer, std::function completionCallback, std::function progressCallback /*= nullptr*/, int timeoutMS /*= -1*/) +void HTTPManager::SendS3PUTRequest(const char* szURI, EIPProtocolVersion protover, std::map& inHeaders, std::vector vecBuffer, std::function completionCallback, std::function progressCallback /*= nullptr*/, int timeoutMS /*= -1*/, int maxSendBytesPerSec /*= 0*/) { CHECK_MAIN_THREAD; HTTPRequest* pRequest = PlatformCreateRequest(EHTTPVerb::HTTP_VERB_PUT, protover, szURI, inHeaders, completionCallback, progressCallback, timeoutMS); pRequest->DisableServiceAuth(); - pRequest->SetPostDataBuffer(vecBuffer); + pRequest->SetMaxSendSpeed(maxSendBytesPerSec); + pRequest->SetPostDataBuffer(std::move(vecBuffer)); m_vecRequestsPendingStart.push_back(pRequest); } @@ -83,119 +84,22 @@ void HTTPManager::Shutdown() } m_vecRequestsPendingStart.clear(); - NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTPManager] Waiting for %d in-flight requests to complete...", (int)m_vecRequestsInFlight.size()); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTPManager] Canceling %d in-flight requests...", (int)m_vecRequestsInFlight.size()); - // Wait for all in-flight requests to complete + // removing handles cancels transfers; resolver workers hold value snapshots, so abandoned results never block shutdown + for (HTTPRequest* request : m_vecRequestsInFlight) + delete request; + m_vecRequestsInFlight.clear(); if (m_pCurl != nullptr) { - int numRunning = 0; - do - { - // Perform any pending operations - curl_multi_perform(m_pCurl, &numRunning); - - // Check for completed requests - int msgq = 0; - CURLMsg* m = nullptr; - while ((m = curl_multi_info_read(m_pCurl, &msgq)) != nullptr) - { - if (m->msg == CURLMSG_DONE) - { - CURL* pCurlHandle = m->easy_handle; - - // Find and remove the associated request - for (auto it = m_vecRequestsInFlight.begin(); it != m_vecRequestsInFlight.end(); ++it) - { - HTTPRequest* pRequest = *it; - if (pRequest != nullptr && pRequest->EasyHandleMatches(pCurlHandle)) - { - // During shutdown, skip invoking the completion callback. The callback - // may reference objects (e.g. NGMP_OnlineServices_StatsInterface via - // captured 'this') that have already been or are being destroyed, - // leading to use-after-free memory corruption and crashes in unrelated - // destructors such as GameSpyMiscPreferences::~GameSpyMiscPreferences(). - // HTTPRequest::~HTTPRequest() handles all necessary curl handle cleanup. - delete pRequest; - m_vecRequestsInFlight.erase(it); - break; - } - } - } - } - - // Small sleep to avoid busy-waiting if there are still operations pending - if (numRunning > 0) - { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - - } while (numRunning > 0 || !m_vecRequestsInFlight.empty()); - - NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTPManager] All in-flight requests completed"); - - // Delete any remaining in-flight requests without invoking their callbacks. - // These are requests that completed via curl but were not matched above, or - // requests that are still pending completion. Invoking callbacks here is unsafe - // as the objects they reference may already be destroyed. - for (HTTPRequest* pRequest : m_vecRequestsInFlight) - { - if (pRequest != nullptr) - { - delete pRequest; - } - } - m_vecRequestsInFlight.clear(); - - // Now safe to cleanup curl_multi_cleanup(m_pCurl); m_pCurl = nullptr; - - // Cleanup libcurl global state - curl_global_cleanup(); } NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTPManager] Shutdown complete"); } -bool HTTPManager::DeterminePlatformProxySettings() -{ - CHECK_MAIN_THREAD; - - WINHTTP_CURRENT_USER_IE_PROXY_CONFIG pProxyConfig; - WinHttpGetIEProxyConfigForCurrentUser(&pProxyConfig); - - if (pProxyConfig.lpszProxy != nullptr) - { - LPWSTR ws = pProxyConfig.lpszProxy; - std::string strFullProxy; - strFullProxy.reserve(wcslen(ws)); - for (; *ws; ws++) - strFullProxy += (char)*ws; - - size_t ipStart = strFullProxy.find("="); - ipStart = (ipStart != std::string::npos) ? ipStart + 1 : 0; - size_t ipEnd = strFullProxy.find(":", ipStart); - if (ipEnd == std::string::npos) ipEnd = strFullProxy.size(); - - m_strProxyAddr = strFullProxy.substr(ipStart, ipEnd - ipStart); - - size_t portStart = ipEnd + 1; - size_t portEnd = strFullProxy.find(";", portStart); - std::string strPort = strFullProxy.substr(portStart, portEnd != std::string::npos ? portEnd - portStart : std::string::npos); - - m_proxyPort = (uint16_t)atoi(strPort.c_str()); - } - - m_bProxyEnabled = pProxyConfig.lpszProxy != nullptr; - - if (pProxyConfig.lpszProxy) GlobalFree(pProxyConfig.lpszProxy); - if (pProxyConfig.lpszAutoConfigUrl) GlobalFree(pProxyConfig.lpszAutoConfigUrl); - if (pProxyConfig.lpszProxyBypass) GlobalFree(pProxyConfig.lpszProxyBypass); - - return m_bProxyEnabled; -} - HTTPRequest* HTTPManager::PlatformCreateRequest(EHTTPVerb httpVerb, EIPProtocolVersion protover, const char* szURI, std::map& inHeaders, std::function completionCallback, std::function progressCallback /*= nullptr*/, int timeoutMS /* = -1 */) noexcept { CHECK_MAIN_THREAD; @@ -216,7 +120,6 @@ void HTTPManager::Initialize() CHECK_MAIN_THREAD; m_pCurl = curl_multi_init(); - m_bProxyEnabled = DeterminePlatformProxySettings(); } void HTTPManager::Tick() @@ -225,13 +128,20 @@ void HTTPManager::Tick() std::vector vecItemsToRemove = std::vector(); - // start anything needing starting - for (HTTPRequest* pRequest : m_vecRequestsPendingStart) + // Callbacks may enqueue new requests; do not iterate the live pending vector. + auto pending = std::move(m_vecRequestsPendingStart); + m_vecRequestsPendingStart.clear(); + for (HTTPRequest* request : pending) { - pRequest->StartRequest(); - m_vecRequestsInFlight.push_back(pRequest); + request->StartRequest(); + m_vecRequestsInFlight.push_back(request); + } + for (HTTPRequest* request : m_vecRequestsInFlight) + { + request->PollConnectionSetup(); + if (request->IsComplete()) + vecItemsToRemove.push_back(request); } - m_vecRequestsPendingStart.clear(); // perform and poll if (m_pCurl == nullptr) @@ -248,7 +158,7 @@ void HTTPManager::Tick() if (pRequest->WaitingDelayAction()) { bool bDone = pRequest->InvokeDelayAction(); - if (bDone) + if (bDone && pRequest->IsComplete()) { vecItemsToRemove.push_back(pRequest); } @@ -277,7 +187,8 @@ void HTTPManager::Tick() pRequest->SetWaitingDelay(m->data.result); #else pRequest->Threaded_SetComplete(m->data.result); - vecItemsToRemove.push_back(pRequest); + if (pRequest->IsComplete()) + vecItemsToRemove.push_back(pRequest); #endif } } @@ -288,16 +199,16 @@ void HTTPManager::Tick() // remove any completed for (HTTPRequest* pRequestToDestroy : vecItemsToRemove) { - m_vecRequestsInFlight.erase(std::remove(m_vecRequestsInFlight.begin(), m_vecRequestsInFlight.end(), pRequestToDestroy)); + m_vecRequestsInFlight.erase(std::remove(m_vecRequestsInFlight.begin(), m_vecRequestsInFlight.end(), pRequestToDestroy), m_vecRequestsInFlight.end()); delete pRequestToDestroy; } } -void HTTPManager::AddHandleToMulti(CURL* pNewHandle) +bool HTTPManager::AddHandleToMulti(CURL* pNewHandle) { CHECK_MAIN_THREAD; - curl_multi_add_handle(m_pCurl, pNewHandle); + return m_pCurl != nullptr && curl_multi_add_handle(m_pCurl, pNewHandle) == CURLM_OK; } void HTTPManager::RemoveHandleFromMulti(CURL* pHandleToRemove) diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.cpp index 02786d7f3d9..cce448bd1c9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.cpp @@ -1,4 +1,5 @@ #include "GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h" +#include "GameNetwork/GeneralsOnline/ServiceEndpointPolicy.h" #include "GameNetwork/GeneralsOnline/OnlineServices_Init.h" #include "GameNetwork/GeneralsOnline/HTTP/HTTPManager.h" #include "GameNetwork/GeneralsOnline/NGMP_interfaces.h" @@ -35,22 +36,14 @@ HTTPRequest::HTTPRequest(EHTTPVerb httpVerb, EIPProtocolVersion protover, const HTTPRequest::~HTTPRequest() { - NGMP_OnlineServicesManager* pMgr = NGMP_OnlineServicesManager::GetInstance(); - if (pMgr == nullptr) - return; - - HTTPManager* pHTTPManager = pMgr->GetHTTPManager(); - pHTTPManager->RemoveHandleFromMulti(m_pCURL); - - curl_easy_cleanup(m_pCURL); - - m_vecBuffer.clear(); - - if (headers) + m_proxyResolution.reset(); + NGMP_OnlineServicesManager* manager = NGMP_OnlineServicesManager::GetInstance(); + if (m_handleAdded && manager != nullptr && manager->GetHTTPManager() != nullptr) { - curl_slist_free_all(headers); - headers = nullptr; - } + manager->GetHTTPManager()->RemoveHandleFromMulti(m_pCURL); + } + curl_easy_cleanup(m_pCURL); + curl_slist_free_all(headers); } @@ -59,7 +52,10 @@ void HTTPRequest::SetPostData(const char* szPostData) // TODO_HTTP: Error if verb isnt post m_strPostData = std::string(szPostData); - NetworkLog(ELogVerbosity::LOG_DEBUG, "[%p|%s|Verb %d] Transfer is created: Body is %s", this, m_strURI.c_str(), m_httpVerb, szPostData); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_DEBUG, "[%p|%s|Verb %d] Transfer is created: Body is %s", this, m_strURI.c_str(), m_httpVerb, szPostData); + } } void HTTPRequest::SetPostDataBuffer(std::vector vecBuffer) @@ -76,7 +72,48 @@ void HTTPRequest::StartRequest() m_currentBufSize_Used = 0; - NetworkLog(ELogVerbosity::LOG_DEBUG, "[%p|%s|Verb %d] Transfer is starting: Body is %s", this, m_strURI.c_str(), m_httpVerb, m_strPostData.c_str()); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_DEBUG, "[%p|%s|Verb %d] Transfer is starting: Body is %s", this, m_strURI.c_str(), m_httpVerb, m_strPostData.c_str()); + } + m_setupStarted = std::chrono::steady_clock::now(); + m_proxyResolution = ServiceConnection::StartProxyResolution( + NGMP_OnlineServicesManager::Settings.Network_GetConnectionSettings(), m_strURI); +} + +void HTTPRequest::PollConnectionSetup() +{ + if (!m_proxyResolution || m_bIsComplete) + return; + if (std::chrono::steady_clock::now() - m_setupStarted >= std::chrono::milliseconds(m_timeoutMS)) + { + m_proxyResolution.reset(); + m_setupFailurePhase = "proxy-timeout"; + Threaded_SetComplete(CURLE_OPERATION_TIMEDOUT); + return; + } + if (!m_proxyResolution->ready.load(std::memory_order_acquire)) + return; + m_proxyPolicy = std::move(m_proxyResolution->result); + m_proxyResolution.reset(); + if (!m_proxyPolicy.error.empty() || m_proxyPolicy.routes.empty()) + { + m_setupFailurePhase = m_proxyPolicy.failurePhase.empty() ? "proxy-policy" : m_proxyPolicy.failurePhase; + NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTP] Proxy policy failed: phase=%s native_code=%lu detail=%s", + m_setupFailurePhase.c_str(), m_proxyPolicy.nativeError, m_proxyPolicy.error.c_str()); + Threaded_SetComplete(CURLE_FAILED_INIT); + return; + } + if (ServiceConnection::ShouldLogProxyPolicy(false, m_proxyPolicy)) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTP] Proxy route policy: %zu route(s), first=%s", + m_proxyPolicy.routes.size(), m_proxyPolicy.routes.front().direct ? "direct" : m_proxyPolicy.routes.front().url.c_str()); + if (!m_proxyPolicy.fallbackReason.empty()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTP] Proxy policy fallback: native_code=%lu detail=%s", + m_proxyPolicy.nativeError, m_proxyPolicy.fallbackReason.c_str()); + } + } PlatformStartRequest(); } @@ -119,7 +156,7 @@ void HTTPRequest::InvokeCallbackIfComplete() { strResponse.clear(); } - m_completionCallback(true, m_responseCode, strResponse, this); + m_completionCallback(m_result == CURLE_OK, static_cast(m_responseCode), strResponse, this); } } } @@ -138,6 +175,7 @@ bool HTTPRequest::InvokeDelayAction() int64_t currTime = std::chrono::duration_cast(std::chrono::utc_clock::now().time_since_epoch()).count(); if (currTime - m_timeRequestComplete > 2000) { + m_timeRequestComplete = -1; Threaded_SetComplete(m_pendingCURLCode); return true; } @@ -150,10 +188,31 @@ bool HTTPRequest::InvokeDelayAction() void HTTPRequest::Threaded_SetComplete(CURLcode result) { - // store response code + long proxyStatus = 0; + curl_easy_getinfo(m_pCURL, CURLINFO_HTTP_CONNECTCODE, &proxyStatus); curl_easy_getinfo(m_pCURL, CURLINFO_RESPONSE_CODE, &m_responseCode); + // CONNECT authentication failures do not populate the origin response code. + if (proxyStatus == 407) + m_responseCode = 407; + if (proxyStatus != 407 && m_responseCode != 407 && (result == CURLE_COULDNT_CONNECT || result == CURLE_COULDNT_RESOLVE_PROXY || result == CURLE_COULDNT_RESOLVE_HOST) + && m_proxyRoute + 1 < m_proxyPolicy.routes.size() + && std::chrono::steady_clock::now() - m_setupStarted < std::chrono::milliseconds(m_timeoutMS)) + { + NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->RemoveHandleFromMulti(m_pCURL); + m_handleAdded = false; + ++m_proxyRoute; + curl_easy_reset(m_pCURL); + curl_slist_free_all(headers); + headers = nullptr; + m_currentBufSize_Used = 0; + m_vecBuffer.resize(g_initialBufSize); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTP] Trying next policy route after pre-connection failure (%d)", result); + PlatformStartRequest(); + return; + } + m_result = result; - if (result == CURLE_OK) + if (result == CURLE_OK && m_proxyPolicy.routes[m_proxyRoute].direct && NGMP_OnlineServicesManager::IsServiceURLTrusted(m_strURI)) { HTTPManager* pHTTPManager = static_cast(NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()); if (pHTTPManager != nullptr) @@ -186,46 +245,63 @@ void HTTPRequest::Threaded_SetComplete(CURLcode result) // finalize the size, so we can use .size etc m_vecBuffer.resize(m_currentBufSize_Used); - std::string strURIRedacted = m_strURI; - -#if !_DEBUG - size_t tokenpos = strURIRedacted.find("token:"); - if (tokenpos != -1) + long protocol = 0; + curl_easy_getinfo(m_pCURL, CURLINFO_HTTP_VERSION, &protocol); + // successful requests are only logged in verbose mode + const bool success = m_setupFailurePhase.empty() && result == CURLE_OK && m_responseCode >= 200 && m_responseCode < 300; + if (!success || g_LogVerbosity == ELogVerbosity::LOG_DEBUG || NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) { - std::string strReplace = ""; - const size_t tokenLen = 32; - strURIRedacted = strURIRedacted.replace(tokenpos + 6, tokenLen, strReplace); - } -#endif + const char* method = "UNKNOWN"; + switch (m_httpVerb) + { + case EHTTPVerb::HTTP_VERB_GET: method = "GET"; break; + case EHTTPVerb::HTTP_VERB_POST: method = "POST"; break; + case EHTTPVerb::HTTP_VERB_PUT: method = "PUT"; break; + case EHTTPVerb::HTTP_VERB_DELETE: method = "DELETE"; break; + } + const auto url = ServiceConnection::RequestURLForLog(m_strURI, NGMP_OnlineServicesManager::GetAPIEndpoint("")); + if (!m_setupFailurePhase.empty()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTP] Completed: method=%s url=%s source=proxy-policy phase=%s curl=not-started native_code=%lu", + method, url.c_str(), m_setupFailurePhase.c_str(), m_proxyPolicy.nativeError); + } + else + { + NetworkLog(success ? ELogVerbosity::LOG_DEBUG : ELogVerbosity::LOG_RELEASE, "[HTTP] Completed: method=%s url=%s phase=%s curl=%d status=%ld proxy_status=%ld protocol=%s bytes=%zu", + method, url.c_str(), ServiceConnection::FailurePhase(result, m_responseCode, proxyStatus), result, m_responseCode, proxyStatus, + ServiceConnection::ProtocolName(protocol), m_currentBufSize_Used); + } - std::string strResponse = std::string(reinterpret_cast(m_vecBuffer.data()), m_currentBufSize_Used); - NetworkLog(ELogVerbosity::LOG_RELEASE, "[%p|%s|Verb %d] Transfer is complete: %d bytes total! Curl result is %d", this, strURIRedacted.c_str(), m_httpVerb, m_currentBufSize_Used, result); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + std::string strResponse(reinterpret_cast(m_vecBuffer.data()), m_currentBufSize_Used); - // if we got an error, set the response code to 0 + #if !_DEBUG + // release builds keep hiding the RNG seed and anything token related even when verbose + static const std::string strSeedKey = "\"RNGSeed\":"; + for (size_t seedPos = strResponse.find(strSeedKey); seedPos != std::string::npos; seedPos = strResponse.find(strSeedKey, seedPos)) + { + size_t valueStart = seedPos + strSeedKey.length(); + size_t valueEnd = strResponse.find_first_of(",}", valueStart); + if (valueEnd == std::string::npos) + break; -#if !_DEBUG - static const std::string strSeedKey = "\"RNGSeed\":"; - for (size_t seedPos = strResponse.find(strSeedKey); seedPos != std::string::npos; seedPos = strResponse.find(strSeedKey, seedPos)) - { - size_t valueStart = seedPos + strSeedKey.length(); - size_t valueEnd = strResponse.find_first_of(",}", valueStart); - if (valueEnd == std::string::npos) - break; + strResponse.replace(valueStart, valueEnd - valueStart, ""); + seedPos = valueStart; + } - strResponse.replace(valueStart, valueEnd - valueStart, ""); - seedPos = valueStart; - } + std::string strResponseLower = strResponse; + std::transform(strResponseLower.begin(), strResponseLower.end(), strResponseLower.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (strResponseLower.find("token") != std::string::npos) + { + strResponse = ""; + } + #endif - std::string strResponseLower = strResponse; - std::transform(strResponseLower.begin(), strResponseLower.end(), strResponseLower.begin(), - [](unsigned char c) { return std::tolower(c); }); - if (strResponseLower.find("token") != std::string::npos) - { - strResponse = ""; + NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTP] Response body for %s: %s", url.c_str(), strResponse.c_str()); + } } -#endif - - NetworkLog(ELogVerbosity::LOG_RELEASE, "[%p|%s|Verb %d] Response was %d - %s!", this, strURIRedacted.c_str(), m_httpVerb, m_responseCode, strResponse.c_str()); // trigger callback InvokeCallbackIfComplete(); @@ -233,99 +309,112 @@ void HTTPRequest::Threaded_SetComplete(CURLcode result) void HTTPRequest::PlatformStartRequest() { - if (m_pCURL) + if (m_pCURL == nullptr) { - HTTPManager* pHTTPManager = static_cast(NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()); - - curl_easy_setopt(m_pCURL, CURLOPT_URL, m_strURI.c_str()); - curl_easy_setopt(m_pCURL, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(m_pCURL, CURLOPT_WRITEDATA, (void*)this); - curl_easy_setopt(m_pCURL, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); - curl_easy_setopt(m_pCURL, CURLOPT_USERAGENT, "GeneralsOnline Client"); - - - curl_easy_setopt(m_pCURL, CURLOPT_CONNECTTIMEOUT_MS, m_timeoutMS); - curl_easy_setopt(m_pCURL, CURLOPT_TIMEOUT_MS, m_timeoutMS); - - curl_easy_setopt(m_pCURL, CURLOPT_HTTP_VERSION, NGMP_OnlineServicesManager::Settings.Network_GetHTTPVersionForCurl()); + Threaded_SetComplete(CURLE_FAILED_INIT); + return; + } + if (m_proxyRoute > 0) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTP] Route %zu: %s", m_proxyRoute + 1, m_proxyPolicy.routes[m_proxyRoute].direct ? "direct" : m_proxyPolicy.routes[m_proxyRoute].url.c_str()); + } + std::string error; + if (!ServiceConnection::ConfigureCurl(m_pCURL, m_strURI, m_proxyPolicy.routes[m_proxyRoute], error)) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTP] Connection policy: %s", error.c_str()); + Threaded_SetComplete(CURLE_FAILED_INIT); + return; + } - if (m_protover == EIPProtocolVersion::DONT_CARE) - { - curl_easy_setopt(m_pCURL, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER); - } - else if (m_protover == EIPProtocolVersion::FORCE_IPV4) - { - curl_easy_setopt(m_pCURL, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - } - else if (m_protover == EIPProtocolVersion::FORCE_IPV6) + // credentials only ever go to the selected service route + const bool trusted = NGMP_OnlineServicesManager::IsServiceURLTrusted(m_strURI); + for (const auto& kvPair : m_mapHeaders) + { + if (_stricmp(kvPair.first.c_str(), "Authorization") == 0 && !trusted) { - curl_easy_setopt(m_pCURL, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[HTTP] Refused credentials outside selected service route"); + Threaded_SetComplete(CURLE_URL_MALFORMAT); + return; } - + } - // Are we authenticated? attach our auth header - NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pAuthInterface != nullptr && pAuthInterface->IsLoggedIn()) - { - if (m_bAppendAuthIfPresent) - { - m_mapHeaders["Authorization"] = "Bearer " + pAuthInterface->GetAuthToken(); - } - } + // Are we authenticated? attach our auth header + NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface(); + if (trusted && m_bAppendAuthIfPresent && pAuthInterface != nullptr && pAuthInterface->IsLoggedIn()) + { + m_mapHeaders["Authorization"] = "Bearer " + pAuthInterface->GetAuthToken(); + } - for (auto& kvPair : m_mapHeaders) + for (const auto& kvPair : m_mapHeaders) + { + const std::string strHeader = kvPair.first + ": " + kvPair.second; + curl_slist* updated = curl_slist_append(headers, strHeader.c_str()); + if (updated == nullptr) { - std::string strHeader = kvPair.first + ": " + kvPair.second; - headers = curl_slist_append(headers, strHeader.c_str()); + Threaded_SetComplete(CURLE_OUT_OF_MEMORY); + return; } - curl_easy_setopt(m_pCURL, CURLOPT_HTTPHEADER, headers); + headers = updated; + } - if (m_httpVerb == EHTTPVerb::HTTP_VERB_POST || m_httpVerb == EHTTPVerb::HTTP_VERB_PUT || m_httpVerb == EHTTPVerb::HTTP_VERB_DELETE) + CURLcode result = CURLE_OK; + auto option = [&](CURLoption key, auto value) { + if (result == CURLE_OK) { - //if (m_strPostData.length() > 0) - { - //char* pEscaped = curl_easy_escape(m_pCURL, m_strPostData.c_str(), m_strPostData.length()); - - if (!m_vecPostDataBuffer.empty()) - { - curl_easy_setopt(m_pCURL, CURLOPT_POSTFIELDS, m_vecPostDataBuffer.data()); - curl_easy_setopt(m_pCURL, CURLOPT_POSTFIELDSIZE, m_vecPostDataBuffer.size()); - } - else - { - curl_easy_setopt(m_pCURL, CURLOPT_POSTFIELDS, m_strPostData.c_str()); - } - } + result = curl_easy_setopt(m_pCURL, key, value); } - - // needed for PUT etc - if (m_httpVerb == EHTTPVerb::HTTP_VERB_PUT) + }; + option(CURLOPT_URL, m_strURI.c_str()); + option(CURLOPT_WRITEDATA, static_cast(this)); + option(CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + option(CURLOPT_USERAGENT, "GeneralsOnline Client"); + // proxy lookup and earlier routes share the request deadline + const auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - m_setupStarted).count(); + const long remaining = static_cast(std::max(1, m_timeoutMS - elapsed)); + option(CURLOPT_CONNECTTIMEOUT_MS, remaining); + option(CURLOPT_TIMEOUT_MS, remaining); + option(CURLOPT_IPRESOLVE, static_cast(m_protover == EIPProtocolVersion::FORCE_IPV4 ? CURL_IPRESOLVE_V4 : + m_protover == EIPProtocolVersion::FORCE_IPV6 ? CURL_IPRESOLVE_V6 : CURL_IPRESOLVE_WHATEVER)); + option(CURLOPT_HTTPHEADER, headers); + + if (m_httpVerb == EHTTPVerb::HTTP_VERB_POST || m_httpVerb == EHTTPVerb::HTTP_VERB_PUT || m_httpVerb == EHTTPVerb::HTTP_VERB_DELETE) + { + if (!m_vecPostDataBuffer.empty()) { - curl_easy_setopt(m_pCURL, CURLOPT_CUSTOMREQUEST, "PUT"); + option(CURLOPT_POSTFIELDS, m_vecPostDataBuffer.data()); + option(CURLOPT_POSTFIELDSIZE_LARGE, static_cast(m_vecPostDataBuffer.size())); } - else if (m_httpVerb == EHTTPVerb::HTTP_VERB_DELETE) + else { - curl_easy_setopt(m_pCURL, CURLOPT_CUSTOMREQUEST, "DELETE"); + option(CURLOPT_POSTFIELDS, m_strPostData.c_str()); + option(CURLOPT_POSTFIELDSIZE_LARGE, static_cast(m_strPostData.size())); } + } -#if _DEBUG - if (pHTTPManager->IsProxyEnabled()) - { - curl_easy_setopt(m_pCURL, CURLOPT_PROXY, pHTTPManager->GetProxyAddress().c_str()); - curl_easy_setopt(m_pCURL, CURLOPT_PROXYPORT, pHTTPManager->GetProxyPort()); - } + if (m_maxSendBytesPerSec > 0) + { + option(CURLOPT_MAX_SEND_SPEED_LARGE, static_cast(m_maxSendBytesPerSec)); + } - curl_easy_setopt(m_pCURL, CURLOPT_SSL_VERIFYPEER, 0); - curl_easy_setopt(m_pCURL, CURLOPT_SSL_VERIFYHOST, 0); - curl_easy_setopt(m_pCURL, CURLOPT_VERBOSE, 1); -#else + // needed for PUT etc + if (m_httpVerb == EHTTPVerb::HTTP_VERB_PUT) + { + option(CURLOPT_CUSTOMREQUEST, "PUT"); + } + else if (m_httpVerb == EHTTPVerb::HTTP_VERB_DELETE) + { + option(CURLOPT_CUSTOMREQUEST, "DELETE"); + } - // Use the OS cert store (needed since we build curl against OpenSSL, not schannel). - curl_easy_setopt(m_pCURL, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); - curl_easy_setopt(m_pCURL, CURLOPT_SSL_VERIFYPEER, 1L); - curl_easy_setopt(m_pCURL, CURLOPT_SSL_VERIFYHOST, 2L); -#endif + if (result != CURLE_OK) + { + Threaded_SetComplete(result); + return; + } - pHTTPManager->AddHandleToMulti(m_pCURL); + m_handleAdded = NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->AddHandleToMulti(m_pCURL); + if (!m_handleAdded) + { + Threaded_SetComplete(CURLE_FAILED_INIT); } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMPGame.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMPGame.cpp index 62f7b703cba..dc9e9307cad 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMPGame.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMPGame.cpp @@ -439,11 +439,6 @@ void NGMPGame::launchGame(void) } -#if defined(GENERALS_ONLINE_HIGH_FPS_RENDER) - TheWritableGlobalData->m_horizontalScrollSpeedFactor = NGMP_OnlineServicesManager::Settings.Camera_MoveSpeedRatio(); - TheWritableGlobalData->m_verticalScrollSpeedFactor = NGMP_OnlineServicesManager::Settings.Camera_MoveSpeedRatio(); -#endif - setGameInProgress(TRUE); for (Int i = 0; i < MAX_SLOTS; ++i) diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp index 4bdfb583a4b..52d9d32efce 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include "GameNetwork/GeneralsOnline/CredentialStore.h" #include "GameNetwork/GameSpyOverlay.h" #include "../json.hpp" @@ -173,12 +173,15 @@ void NGMP_OnlineServices_AuthInterface::SendMiddlewareToken(std::string strMWTok void NGMP_OnlineServices_AuthInterface::OnRefreshTokenFailed(const char* szReason, const std::string& strBody) { - // the body can hold a rotated refresh token, so release builds log only its size -#if _DEBUG - NetworkLog(ELogVerbosity::LOG_RELEASE, "[AUTH]: Refresh response body: %s", strBody.c_str()); -#else - NetworkLog(ELogVerbosity::LOG_RELEASE, "[AUTH]: Refresh response body was %zu bytes", strBody.size()); -#endif + // the body can hold a rotated refresh token, so only its size is logged unless verbose + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[AUTH]: Refresh response body: %s", strBody.c_str()); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[AUTH]: Refresh response body was %zu bytes", strBody.size()); + } if (m_currentRefreshAttempt < m_maxRefreshAttempts) { @@ -302,6 +305,33 @@ void NGMP_OnlineServices_AuthInterface::RefreshToken() } void NGMP_OnlineServices_AuthInterface::BeginLogin() +{ + auto* manager = NGMP_OnlineServicesManager::GetInstance(); + if (!manager) + { + return; + } + manager->EnsureServiceRoute([](bool success) + { + auto* auth = NGMP_OnlineServicesManager::GetInterface(); + if (!auth) + { + return; + } + if (success) + { + auth->BeginLoginOnSelectedRoute(); + } + else + { + ClearGSMessageBoxes(); + const bool proxyAuth = NGMP_OnlineServicesManager::GetInstance() != nullptr && NGMP_OnlineServicesManager::GetInstance()->RouteFailedProxyAuth(); + GSMessageBoxOk(UnicodeString(L"Connection failed"), UnicodeString(proxyAuth ? L"The proxy did not accept the login. Try again, or check the proxy settings in Options." : L"No trusted service route could be reached. Check Connection settings in Options, then try again."), []() { TheShell->pop(); }); + } + }); +} + +void NGMP_OnlineServices_AuthInterface::BeginLoginOnSelectedRoute() { m_tokenCreationTime = -1; @@ -431,7 +461,10 @@ void NGMP_OnlineServices_AuthInterface::DoFullLoginFlow() m_lastCheckCode = std::chrono::duration_cast(std::chrono::utc_clock::now().time_since_epoch()).count(); m_strCode = authResp.login_code; - NetworkLog(ELogVerbosity::LOG_DEBUG, "Login Code is %s", m_strCode.c_str()); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_DEBUG, "Login Code is %s", m_strCode.c_str()); + } #if defined(USE_TEST_ENV) std::string strURI = std::format("https://www.playgenerals.online/login/?gamecode={}&env=test", m_strCode.c_str()); @@ -536,9 +569,10 @@ void NGMP_OnlineServices_AuthInterface::Tick() nlohmann::json jsonObject = nlohmann::json::parse(strBody); AuthResponse authResp = jsonObject.get(); -#if _DEBUG - NetworkLog(ELogVerbosity::LOG_RELEASE, "PageBody: %s", strBody.c_str()); -#endif + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "PageBody: %s", strBody.c_str()); + } if (authResp.result == EAuthResponseResult::CODE_INVALID) { NetworkLog(ELogVerbosity::LOG_RELEASE, "LOGIN: Code didnt exist, trying again soon"); @@ -613,14 +647,8 @@ void NGMP_OnlineServices_AuthInterface::LogoutOfMyAccount() std::map mapHeaders; NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendDELETERequest(strURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, "", nullptr); - // delete local credentials cache - std::string strCredentialsCachePath = GetCredentialsFilePath(); - - std::error_code ec; - if (std::filesystem::exists(strCredentialsCachePath, ec)) - { - std::filesystem::remove(strCredentialsCachePath, ec); - } + // delete the stored login token + ServiceConnection::DeleteRefreshToken(CredentialServerKey()); } void NGMP_OnlineServices_AuthInterface::LoginAsSecondaryDevAccount() @@ -633,55 +661,8 @@ void NGMP_OnlineServices_AuthInterface::SaveCredentials(const char* szRefreshTok m_strRefreshToken = std::string(szRefreshToken); // store the new refresh token, we'll need it for the next refresh m_tokenCreationTime = std::chrono::duration_cast(std::chrono::utc_clock::now().time_since_epoch()).count(); - // store in data dir - nlohmann::json root = { {"refresh_token", szRefreshToken} }; - - std::string strData = root.dump(1); - - // encrypt before touching the file so a failure doesn't truncate the credentials already stored - std::string strFileData; -#if defined(GENERALS_ONLINE_ENCRYPT_CREDENTIALS) - DATA_BLOB inputBlob; - DATA_BLOB outputBlob; - - inputBlob.pbData = (BYTE*)strData.c_str(); - inputBlob.cbData = static_cast(strData.size()); - - if (CryptProtectData(&inputBlob, L"GO Credentials", nullptr, nullptr, nullptr, 0, &outputBlob)) - { - strFileData.assign((const char*)outputBlob.pbData, outputBlob.cbData); - LocalFree(outputBlob.pbData); - } - else - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Failed to encrypt credentials, keeping the stored ones"); - return; - } -#else - strFileData = strData; -#endif - - // write to a temp file and swap it in so a failed write never leaves truncated credentials - std::string strCredentialsPath = GetCredentialsFilePath(); - std::string strTempPath = strCredentialsPath + ".tmp"; - bool bSaved = false; - FILE* file = fopen(strTempPath.c_str(), "wb"); - if (file) - { - bSaved = fwrite(strFileData.data(), 1, strFileData.size(), file) == strFileData.size(); - if (fclose(file) != 0) - bSaved = false; - } - - if (bSaved) - bSaved = MoveFileExA(strTempPath.c_str(), strCredentialsPath.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0; - - if (!bSaved) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Failed to save credentials"); - std::error_code ec; - std::filesystem::remove(strTempPath, ec); - } + // a failed save keeps the token for this session + ServiceConnection::SaveRefreshToken(CredentialServerKey(), m_strRefreshToken); } bool NGMP_OnlineServices_AuthInterface::GetCredentials() @@ -689,75 +670,18 @@ bool NGMP_OnlineServices_AuthInterface::GetCredentials() #if defined(_DEBUG) && !defined(USE_TEST_ENV) && !defined(USE_DEBUG_ON_LIVE_SERVER) return false; #endif - std::vector vecBytes; - FILE* file = fopen(GetCredentialsFilePath().c_str(), "rb"); - if (file) - { - fseek(file, 0, SEEK_END); - long fileSize = ftell(file); - fseek(file, 0, SEEK_SET); - if (fileSize > 0) - { - vecBytes.resize(fileSize); - fread(vecBytes.data(), 1, fileSize, file); - } - fclose(file); - } - - - if (!vecBytes.empty()) + std::string strToken; + if (!ServiceConnection::LoadRefreshToken(CredentialServerKey(), strToken)) { - // needs decrypt first -#if defined(GENERALS_ONLINE_ENCRYPT_CREDENTIALS) - DATA_BLOB encryptedBlob; - encryptedBlob.pbData = const_cast(vecBytes.data()); - encryptedBlob.cbData = static_cast(vecBytes.size()); - std::string strJSON; - - DATA_BLOB decryptedBlob = { 0 }; - if (CryptUnprotectData(&encryptedBlob, nullptr, nullptr, nullptr, nullptr, 0, &decryptedBlob)) - { - strJSON = std::string((char*)decryptedBlob.pbData, decryptedBlob.cbData); - LocalFree(decryptedBlob.pbData); // Free memory allocated by CryptUnprotectData - } - else - { - // TODO_JWT: Handle failure - } -#else - std::string strJSON = std::string((char*)vecBytes.data(), vecBytes.size()); -#endif - - - nlohmann::json jsonCredentials = nullptr; - - try - { - jsonCredentials = nlohmann::json::parse(strJSON); - - if (jsonCredentials != nullptr) - { - if (jsonCredentials.contains("refresh_token")) - { - m_strRefreshToken = jsonCredentials["refresh_token"]; - - if (m_strRefreshToken.empty()) - { - return false; - } - - return true; - } - } - - } - catch (...) - { - return false; - } + return false; } + m_strRefreshToken = strToken; + return true; +} - return false; +std::string NGMP_OnlineServices_AuthInterface::CredentialServerKey() +{ + return ServiceConnection::CredentialServerKey(NGMP_OnlineServicesManager::Settings.Network_GetConnectionSettings()); } std::string NGMP_OnlineServices_AuthInterface::GetCredentialsFilePath() diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp index cfe0835efd4..7d77d4b9a53 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp @@ -15,15 +15,21 @@ #include "GameClient/Display.h" #include "WW3D2/surfaceclass.h" #include "WW3D2/dx8wrapper.h" +#include #include #include +#include "GameNetwork/GeneralsOnline/ServiceEndpointPolicy.h" +#include "GameNetwork/GeneralsOnline/ProxyLogin.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #define STB_IMAGE_RESIZE_IMPLEMENTATION #include "GameNetwork/GeneralsOnline/Vendor/stb_image/stb_image_write.h" #include "GameNetwork/GeneralsOnline/Vendor/stb_image/stb_image_resize.h" #include "GameClient/GameText.h" +#include "GameLogic/GameLogic.h" #include +#include +#include extern "C" { @@ -99,6 +105,34 @@ void NGMP_OnlineServicesManager::GetAndParseServiceConfig(std::function(); + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pAuthInterface != nullptr && pLobbyInterface != nullptr) + { + const int64_t localUserID = pAuthInterface->GetUserID(); + for (const LobbyMemberEntry& lobbyMember : pLobbyInterface->GetMembersListForCurrentRoom()) + { + if (lobbyMember.user_id == localUserID) + { + slotIndex = lobbyMember.m_SlotIndex < MAX_SLOTS ? lobbyMember.m_SlotIndex : 0; + break; + } + } + } + + const int slotOffsetMS = std::clamp(NGMP_OnlineServicesManager::GetInstance()->GetServiceConfig().screenshot_slot_offset_ms, 0, 10000); + + static thread_local std::mt19937 rng{ std::random_device{}() }; + const int jitterMS = std::uniform_int_distribution(0, 500)(rng); + + return std::chrono::steady_clock::now() + std::chrono::milliseconds(slotIndex * slotOffsetMS + jitterMS); +} + void NGMP_OnlineServicesManager::CaptureScreenshotForProbe(EScreenshotType screenshotType, std::string strURI) { NGMP_OnlineServicesManager* pOnlineServicesMgr = NGMP_OnlineServicesManager::GetInstance(); @@ -114,7 +148,8 @@ void NGMP_OnlineServicesManager::CaptureScreenshotForProbe(EScreenshotType scree if (pLobbyInterface != nullptr) { const uint64_t matchID = pLobbyInterface->GetCurrentMatchID(); - NGMP_OnlineServicesManager::GetInstance()->CaptureScreenshot(true, [strURI = std::move(strURI), screenshotType, matchID](std::vector vecData) + const std::chrono::steady_clock::time_point uploadTime = GetScreenshotUploadTime(); + NGMP_OnlineServicesManager::GetInstance()->CaptureScreenshot(true, [strURI = std::move(strURI), screenshotType, matchID, uploadTime](std::vector vecData) { CHECK_WORKER_THREAD; @@ -127,7 +162,7 @@ void NGMP_OnlineServicesManager::CaptureScreenshotForProbe(EScreenshotType scree // certain screenshots require caching for later upload when we have a valid match id, so we store them if (screenshotType == EScreenshotType::SCREENSHOT_TYPE_LOADSCREEN) { - NGMP_OnlineServicesManager::GetInstance()->CacheScreenshotBytes_StartMatch(vecData); + NGMP_OnlineServicesManager::GetInstance()->CacheScreenshotBytes_StartMatch(std::move(vecData)); } else if (screenshotType == EScreenshotType::SCREENSHOT_TYPE_SCORESCREEN) { @@ -143,7 +178,8 @@ void NGMP_OnlineServicesManager::CaptureScreenshotForProbe(EScreenshotType scree newEntry.vecBytes = std::move(vecData); newEntry.strSignedURI = strURI; newEntry.screenshotType = screenshotType; - m_vecGuardedSSData.push_back(newEntry); + newEntry.notBefore = uploadTime; + m_vecGuardedSSData.push_back(std::move(newEntry)); } }); } @@ -173,33 +209,146 @@ GenOnlineSettings NGMP_OnlineServicesManager::Settings; NGMP_OnlineServicesManager::NGMP_OnlineServicesManager() { NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Init"); +#if _DEBUG + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] WARNING: TLS verification is disabled in debug builds"); +#endif m_pOnlineServicesManager = this; } +static const char* ServiceEnvironment() +{ + switch (NGMP_OnlineServicesManager::g_Environment) + { + case NGMP_OnlineServicesManager::EEnvironment::DEV: return "dev"; + case NGMP_OnlineServicesManager::EEnvironment::TEST: return "test"; + default: return "prod"; + } +} + std::string NGMP_OnlineServicesManager::GetAPIEndpoint(const char* szEndpoint) { - if (g_Environment == EEnvironment::DEV) + auto* manager = GetInstance(); + if (manager && !manager->m_serviceBase.empty()) + { + return manager->m_serviceBase + szEndpoint; + } + const auto routes = ServiceConnection::BootstrapRoutes(ServiceEnvironment(), Settings.Network_GetConnectionSettings(), Settings.Network_GetLastRoute()); + return routes.empty() ? std::string() : routes.front() + szEndpoint; +} + +bool NGMP_OnlineServicesManager::IsServiceURLTrusted(const std::string& url) +{ + return ServiceConnection::IsTrustedServiceURL(url, GetAPIEndpoint(""), false); +} + +bool NGMP_OnlineServicesManager::IsWebSocketURLTrusted(const std::string& url) +{ + return ServiceConnection::IsTrustedServiceURL(url, GetAPIEndpoint(""), true); +} + +void NGMP_OnlineServicesManager::EnsureServiceRoute(std::function callback) +{ + // never change the route of an authenticated session behind its caller + if (m_pAuthInterface && m_pAuthInterface->IsLoggedIn()) { - return std::format("https://localhost:9000/env/dev/contract/1/{}", szEndpoint); + callback(!m_serviceBase.empty()); + return; } - else if (g_Environment == EEnvironment::TEST) + const auto settings = Settings.Network_GetConnectionSettings(); + const std::string key = settings.serverMode + "|" + settings.serverAddress + "|" + settings.proxyMode + "|" + settings.proxyURL + (settings.windowsLogin ? "|1" : "|0"); + if (key == m_routeSettingsKey && !m_serviceBase.empty() && std::chrono::steady_clock::now() - m_routeValidatedAt < std::chrono::seconds(30)) { - return std::format("https://api.playgenerals.online:2087/env/test/contract/1/{}", szEndpoint); + callback(true); + return; } - else // PROD + m_routeCallbacks.push_back(std::move(callback)); + if (m_routeCallbacks.size() > 1) { - if (NGMP_OnlineServicesManager::Settings.Network_UseAlternativeEndpoint()) - { - return std::format("https://api-ru.playgenerals.online/env/prod/contract/1/{}", szEndpoint); - } - else - { - return std::format("https://api.playgenerals.online/env/prod/contract/1/{}", szEndpoint); - } + return; + } + m_routeSettingsKey = key; + m_routeProxyPrompted = false; + m_routeProxyAuthFailed = false; + m_bootstrapRoutes = ServiceConnection::BootstrapRoutes(ServiceEnvironment(), settings, Settings.Network_GetLastRoute()); + m_bootstrapIndex = 0; + ProbeServiceRoute(); +} + +void NGMP_OnlineServicesManager::FinishServiceRoute(bool success) +{ + auto callbacks = std::move(m_routeCallbacks); + m_routeCallbacks.clear(); + for (auto& callback : callbacks) + { + callback(success); } } +void NGMP_OnlineServicesManager::ProbeServiceRoute() +{ + if (m_bootstrapIndex >= m_bootstrapRoutes.size()) + { + m_serviceBase.clear(); + FinishServiceRoute(false); + return; + } + + // anonymous request: no credentials are sent until a route is validated + m_serviceBase = m_bootstrapRoutes[m_bootstrapIndex]; + const std::string url = m_serviceBase + "VersionManifest"; + std::map headers; + m_pHTTPManager->SendGETRequest(url.c_str(), EIPProtocolVersion::DONT_CARE, headers, + [](bool success, int status, std::string body, HTTPRequest* request) + { + auto* manager = GetInstance(); + if (!manager) + { + return; + } + // the proxy wants a login: ask once per attempt, then probe the same route again + const ServiceConnection::ProxyRoute route = status == 407 && request != nullptr ? request->GetProxyRoute() : ServiceConnection::ProxyRoute(); + if (!route.direct) + { + manager->m_routeProxyAuthFailed = true; + if (!manager->m_routeProxyPrompted) + { + manager->m_routeProxyPrompted = true; + ServiceConnection::RequestProxyLogin(route, [](bool loggedIn) + { + auto* manager = GetInstance(); + if (!manager) + { + return; + } + if (!loggedIn) + { + manager->m_bootstrapIndex = manager->m_bootstrapRoutes.size(); + } + manager->ProbeServiceRoute(); + }); + return; + } + } + if (success && status == 200 && ServiceConnection::IsExpectedVersionManifest(body)) + { + manager->m_routeProxyAuthFailed = false; + manager->m_routeValidatedAt = std::chrono::steady_clock::now(); + if (g_Environment == EEnvironment::PROD && Settings.Network_GetConnectionSettings().serverMode != "custom") + { + Settings.Network_SaveLastRoute(ServiceConnection::RouteHost(manager->m_serviceBase)); + } + NetworkLog(ELogVerbosity::LOG_RELEASE, "[Connection] Trusted service route validated: %s", manager->m_serviceBase.c_str()); + manager->FinishServiceRoute(true); + return; + } + NetworkLog(ELogVerbosity::LOG_RELEASE, "[Connection] Route validation failed (HTTP %d); credentials were not sent", status); + // an access or proxy rejection is not evidence that another route would work + manager->m_bootstrapIndex = (status == 401 || status == 403 || status == 407) ? manager->m_bootstrapRoutes.size() : manager->m_bootstrapIndex + 1; + manager->ProbeServiceRoute(); + }, nullptr, 6000); +} + void NGMP_OnlineServicesManager::AttemptLoadSteam() { // app id for ZH @@ -349,7 +498,7 @@ void NGMP_OnlineServicesManager::Shutdown() // Shutdown and completely destroy WebSocket BEFORE cleaning up HTTPManager // This is critical because WebSocket has curl handles that must be freed - // before curl_global_cleanup() is called by HTTPManager + // before the HTTP multi handle is destroyed. if (m_pWebSocket) { NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Shutting down WebSocket..."); @@ -361,7 +510,7 @@ void NGMP_OnlineServicesManager::Shutdown() NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] WebSocket shutdown complete"); } - // Now safe to shutdown HTTP manager which calls curl_global_cleanup() + // cancel remaining HTTP requests if (m_pHTTPManager != nullptr) { NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Shutting down HTTPManager..."); @@ -375,6 +524,22 @@ void NGMP_OnlineServicesManager::Shutdown() } void NGMP_OnlineServicesManager::StartVersionCheck(std::function fnCallback) +{ + EnsureServiceRoute([fnCallback](bool success) + { + auto* manager = GetInstance(); + if (!manager) + return; + if (!success) + { + fnCallback(false, false); + return; + } + manager->StartVersionCheckOnSelectedRoute(fnCallback); + }); +} + +void NGMP_OnlineServicesManager::StartVersionCheckOnSelectedRoute(std::function fnCallback) { std::string strURI = NGMP_OnlineServicesManager::GetAPIEndpoint("VersionCheck"); @@ -579,7 +744,6 @@ void NGMP_OnlineServicesManager::CaptureScreenshot(bool bResizeForTransmit, std: } SurfaceClass* surface = DX8Wrapper::_Get_DX8_Back_Buffer(); - LPDIRECT3DSURFACE8 surf = nullptr; SurfaceClass* surfaceCopy = nullptr; void* pBits = nullptr; IDirect3DSurface8* pDXsurf = nullptr; @@ -599,119 +763,96 @@ void NGMP_OnlineServicesManager::CaptureScreenshot(bool bResizeForTransmit, std: { DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), NULL, 0, surfaceCopy->Peek_D3D_Surface(), NULL); - HRESULT hr; + // gather all our data + int pitch = 0; + pBits = surfaceCopy->Lock(&pitch); - D3DDISPLAYMODE mode; - if (SUCCEEDED(hr = DX8Wrapper::_Get_D3D_Device8()->GetDisplayMode(&mode))) + if (pBits != nullptr) { - if (SUCCEEDED(hr = DX8Wrapper::_Get_D3D_Device8()->CreateImageSurface(mode.Width, mode.Height, - D3DFMT_A8R8G8B8, &surf))) - { - if (SUCCEEDED(hr = DX8Wrapper::_Get_D3D_Device8()->GetFrontBuffer(surf))) + int width = surfaceDesc.Width; + int height = surfaceDesc.Height; + + // Copy pixel data into an owned buffer before spawning the thread so + // the surface can be safely unlocked on the main thread immediately after. + const uint8_t* pSurfaceBytes = static_cast(pBits); + std::vector pixelData(pSurfaceBytes, pSurfaceBytes + height * pitch); + + // process on thread - track the thread so we can join it during shutdown + std::thread* pNewThread = new std::thread([cbOnDataAvailable, width, height, pixelData = std::move(pixelData), pitch, bResizeForTransmit]() mutable { - // gather all our data - int pitch = 0; - pBits = surfaceCopy->Lock(&pitch); + CHECK_WORKER_THREAD; - if (pBits != nullptr) - { - int width = surfaceDesc.Width; - int height = surfaceDesc.Height; - - // Copy pixel data into an owned buffer before spawning the thread so - // the surface can be safely unlocked on the main thread immediately after. - std::vector pixelData(height * pitch); - memcpy(pixelData.data(), pBits, height * pitch); - - // process on thread - track the thread so we can join it during shutdown - std::thread* pNewThread = new std::thread([cbOnDataAvailable, width, height, pixelData = std::move(pixelData), pitch, bResizeForTransmit]() - { - CHECK_WORKER_THREAD; - - unsigned char* rgbData = new unsigned char[width * height * 3]; - - std::vector vecData; - - int finalWidth = width; - int finalHeight = height; - - for (int y = 0; y < height; ++y) { - const uint8_t* row = pixelData.data() + y * pitch; - int rowOffset = y * width * 3; - int srcOffset = 0; - for (int x = 0; x < width; ++x, srcOffset += 4) - { - int dstIndex = rowOffset + x * 3; - rgbData[dstIndex + 0] = row[srcOffset + 2]; // R - rgbData[dstIndex + 1] = row[srcOffset + 1]; // G - rgbData[dstIndex + 2] = row[srcOffset + 0]; // B - } - } - - // resize - unsigned char* pBufferToWrite = rgbData; - if (bResizeForTransmit) - { - ServiceConfig& serviceConf = NGMP_OnlineServicesManager::GetInstance()->GetServiceConfig(); - int new_width = serviceConf.screenshot_width; - int new_height = serviceConf.screenshot_height; - int channels = 3; - unsigned char* resized = new unsigned char[new_width * new_height * channels]; - - stbir_resize_uint8(rgbData, width, height, 0, - resized, new_width, new_height, 0, - channels - ); - - // update data - finalWidth = new_width; - finalHeight = new_height; - pBufferToWrite = resized; - } - // end resize - - stbi_write_jpg_to_func([](void* context, void* data, int size) - { - std::vector* buffer = static_cast*>(context); - buffer->insert(buffer->end(), (unsigned char*)data, (unsigned char*)data + size); - }, &vecData, finalWidth, finalHeight, 3, pBufferToWrite, bResizeForTransmit ? 0 : 90); - - // cleanup - if (bResizeForTransmit) - { - delete[] pBufferToWrite; // This is 'resized' - pBufferToWrite = nullptr; - } - - delete[] rgbData; - rgbData = nullptr; - - // invoke cb - if (cbOnDataAvailable != nullptr) - { - cbOnDataAvailable(vecData); - } - } - ); + const int channels = 3; + int finalWidth = width; + int finalHeight = height; - // Store the thread so we can join it during shutdown - // SECURITY FIX: Capture manager pointer before spawning thread to avoid TOCTOU race - NGMP_OnlineServicesManager* pMgr = NGMP_OnlineServicesManager::GetInstance(); - if (pMgr != nullptr) + // Pack the BGRA rows to tight RGB in place. The write position never passes the read position. + uint8_t* pixels = pixelData.data(); + for (int y = 0; y < height; ++y) { - std::scoped_lock lock(pMgr->m_mutexScreenshotThreads); - pMgr->m_vecScreenshotThreads.push_back(pNewThread); + const uint8_t* src = pixels + y * pitch; + uint8_t* dst = pixels + y * width * channels; + for (int x = 0; x < width; ++x, src += 4, dst += channels) + { + const uint8_t blue = src[0]; + dst[0] = src[2]; // R + dst[1] = src[1]; // G + dst[2] = blue; // B + } } - else + + // resize + std::vector resized; + if (bResizeForTransmit) { - // Manager was destroyed, cannot store thread. Thread will leak but won't crash. - NetworkLog(ELogVerbosity::LOG_RELEASE, "[Screenshot] Manager destroyed before thread could be registered"); + ServiceConfig& serviceConf = NGMP_OnlineServicesManager::GetInstance()->GetServiceConfig(); + finalWidth = serviceConf.screenshot_width; + finalHeight = serviceConf.screenshot_height; + resized.resize(finalWidth * finalHeight * channels); + + stbir_resize_uint8(pixels, width, height, 0, + resized.data(), finalWidth, finalHeight, 0, + channels + ); + + // the capture is no longer needed + std::vector().swap(pixelData); } + // end resize + + // room for a typical JPEG, so the write callback rarely has to grow it + std::vector vecData; + vecData.reserve(256 * 1024); + + stbi_write_jpg_to_func([](void* context, void* data, int size) + { + std::vector* buffer = static_cast*>(context); + buffer->insert(buffer->end(), (unsigned char*)data, (unsigned char*)data + size); + }, &vecData, finalWidth, finalHeight, channels, bResizeForTransmit ? resized.data() : pixelData.data(), bResizeForTransmit ? 0 : 90); - bSucceeded = true; + // invoke cb + if (cbOnDataAvailable != nullptr) + { + cbOnDataAvailable(std::move(vecData)); } } + ); + + // Store the thread so we can join it during shutdown + // SECURITY FIX: Capture manager pointer before spawning thread to avoid TOCTOU race + NGMP_OnlineServicesManager* pMgr = NGMP_OnlineServicesManager::GetInstance(); + if (pMgr != nullptr) + { + std::scoped_lock lock(pMgr->m_mutexScreenshotThreads); + pMgr->m_vecScreenshotThreads.push_back(pNewThread); } + else + { + // Manager was destroyed, cannot store thread. Thread will leak but won't crash. + NetworkLog(ELogVerbosity::LOG_RELEASE, "[Screenshot] Manager destroyed before thread could be registered"); + } + + bSucceeded = true; } } } @@ -719,18 +860,9 @@ void NGMP_OnlineServicesManager::CaptureScreenshot(bool bResizeForTransmit, std: // clean everything up, whether we succeeded or not - // release the image surface - if (surf != nullptr) - { - surf->Release(); - //delete surf; - surf = nullptr; - } - // unlock if (surface != nullptr) { - surface->Unlock(); surface->Release_Ref(); surface = nullptr; } @@ -865,8 +997,15 @@ void NGMP_OnlineServicesManager::OnLogin(ELoginResult loginResult, const char* s // connect to WS m_pWebSocket = std::make_shared(); - // TODO_NGMP: This should come from the service, if the service was russia-aware - std::string strWebsocketAddr = NGMP_OnlineServicesManager::Settings.Network_UseAlternativeEndpoint() ? "wss://api-ru.playgenerals.online/ws" : std::string(szWSAddr); + // the websocket address comes from the route we logged in through, not from the service + const std::string strWebsocketAddr = ServiceConnection::WebSocketURLForBase(GetAPIEndpoint("")); + const std::string strServiceAddr = szWSAddr ? szWSAddr : ""; + if (strServiceAddr != strWebsocketAddr) + { + const std::string strServiceHost = ServiceConnection::RouteHost(strServiceAddr); + const bool bPrintable = std::none_of(strServiceHost.begin(), strServiceHost.end(), [](unsigned char c) { return c <= 32 || c >= 127; }); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[Connection] Service websocket address (host %s) differs from the route, using %s", bPrintable ? strServiceHost.c_str() : "[invalid]", strWebsocketAddr.c_str()); + } m_pWebSocket->Connect(strWebsocketAddr.c_str(), false, [=]() { @@ -953,20 +1092,35 @@ void NGMP_OnlineServicesManager::Tick() std::scoped_lock ssLock(m_ScreenshotMutex); + const int uploadBytesPerSec = std::clamp(m_ServiceConfig.screenshot_upload_bytes_per_sec, 16384, 10485760); + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const bool bMatchOver = TheGameLogic == nullptr || !TheGameLogic->isInMultiplayerGame(); // uploads no longer compete with game traffic + // screenshot types that already have a presigned URL - for (S3ScreenshotEntry screenshotEntry : m_vecGuardedSSData) + for (auto it = m_vecGuardedSSData.begin(); it != m_vecGuardedSSData.end();) { + S3ScreenshotEntry& screenshotEntry = *it; + const bool bUnthrottled = bMatchOver || screenshotEntry.screenshotType == EScreenshotType::SCREENSHOT_TYPE_SCORESCREEN; + + // not this client's turn to upload yet + if (!bUnthrottled && screenshotEntry.notBefore > now) + { + ++it; + continue; + } + // NOTE: Screenshot types start and end of match are captures and cached in memory until the server tells us where to upload them, so we need to wait for the upload URI if ((screenshotEntry.screenshotType == EScreenshotType::SCREENSHOT_TYPE_LOADSCREEN || screenshotEntry.screenshotType == EScreenshotType::SCREENSHOT_TYPE_SCORESCREEN) && screenshotEntry.strSignedURI.empty()) { + it = m_vecGuardedSSData.erase(it); continue; } else // we have the data we need, send away { std::map mapHeaders; mapHeaders["Content-Type"] = "image/jpeg"; - NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendS3PUTRequest(screenshotEntry.strSignedURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, screenshotEntry.vecBytes, [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq) + NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendS3PUTRequest(screenshotEntry.strSignedURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, std::move(screenshotEntry.vecBytes), [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq) { #if _DEBUG if (statusCode != 200) @@ -975,12 +1129,11 @@ void NGMP_OnlineServicesManager::Tick() } #endif NetworkLog(ELogVerbosity::LOG_RELEASE, "Screenshot upload, result: %d", statusCode); - }, nullptr, HTTP_UPLOAD_TIMEOUT); + }, nullptr, HTTP_UPLOAD_TIMEOUT, bUnthrottled ? 0 : uploadBytesPerSec); } - - } - m_vecGuardedSSData.clear(); + it = m_vecGuardedSSData.erase(it); + } // screenshots and replays that are cached and awaiting S3 url, and now have said URL if (!m_vecCachedScreenshotBytes_MatchStart.empty()) // we have data waiting @@ -990,9 +1143,10 @@ void NGMP_OnlineServicesManager::Tick() // queue it S3ScreenshotEntry newEntry; newEntry.screenshotType = EScreenshotType::SCREENSHOT_TYPE_LOADSCREEN; - newEntry.vecBytes = m_vecCachedScreenshotBytes_MatchStart; - newEntry.strSignedURI = m_strCachedScreenshot_MatchStart_S3URI; - m_vecGuardedSSData.push_back(newEntry); + newEntry.vecBytes = std::move(m_vecCachedScreenshotBytes_MatchStart); + newEntry.strSignedURI = std::move(m_strCachedScreenshot_MatchStart_S3URI); + newEntry.notBefore = GetScreenshotUploadTime(); + m_vecGuardedSSData.push_back(std::move(newEntry)); // clear data m_vecCachedScreenshotBytes_MatchStart = std::vector(); @@ -1023,7 +1177,7 @@ void NGMP_OnlineServicesManager::Tick() // do the upload std::map mapHeaders; mapHeaders["Content-Type"] = "application/octet-stream"; - NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendS3PUTRequest(m_cachedReplayUpload.signedURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, m_cachedReplayUpload.bytes, [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq) + NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendS3PUTRequest(m_cachedReplayUpload.signedURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, std::move(m_cachedReplayUpload.bytes), [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq) { #if _DEBUG if (statusCode != 200) @@ -1074,7 +1228,12 @@ void NGMP_OnlineServicesManager::InitSentry() // Initialize libcurl global state here, before any plugins (e.g. EasyAntiCheat) are loaded. // This ensures libcurl's internal mutexes are fully initialized before the EAC plugin // attempts to use them, preventing an access violation in mtx_do_lock on null mutex state. - curl_global_init(CURL_GLOBAL_DEFAULT); + static const CURLcode curlInitialization = curl_global_init(CURL_GLOBAL_DEFAULT); + if (curlInitialization != CURLE_OK) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[Connection] curl initialization failed (%d)", curlInitialization); + return; + } #if !_DEBUG // game paths are in the process code page, sentry's char* API wants UTF-8, so hand it the wide path @@ -1176,6 +1335,7 @@ void WebSocket::Shutdown() // Signal that we're shutting down m_bShuttingDown = true; + m_proxyResolution.reset(); // Disconnect from the websocket (handles the connected case) Disconnect(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp index 07e13867223..9d215c80896 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp @@ -1,4 +1,6 @@ +#include "GameNetwork/GeneralsOnline/ProxyLogin.h" #include +#include "GameNetwork/GeneralsOnline/ServiceEndpointPolicy.h" #include "GameNetwork/GeneralsOnline/NGMP_interfaces.h" #include "GameNetwork/GeneralsOnline/NGMP_include.h" #include "GameNetwork/GeneralsOnline/NetworkPacket.h" @@ -9,10 +11,10 @@ #include "../HTTP/HTTPManager.h" #include "GameNetwork/GameSpy/PeerDefs.h" -// one clock for every websocket timestamp; utc_clock also loads the time zone database on MSVC +// steady clock, so deadlines do not move with system clock changes static int64_t NowMs() { - return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); } // ----------------------------- @@ -138,103 +140,169 @@ WebSocket::~WebSocket() int WebSocket::Ping() { - size_t sent; - CURLcode result = curl_ws_send(m_pCurlWS, "wsping", strlen("wsping"), &sent, 0, - CURLWS_PING); + if (!m_bConnected) + { + return CURLE_AGAIN; + } + // keep the app heartbeat for older services, no raw PING, at most one queued under backpressure nlohmann::json j; j["msg_id"] = EWebSocketMessageID::PING; - std::string strBody = j.dump(); - - Send(strBody.c_str()); - - return (int)result; + QueueOutbound(j.dump(), true); + return CURLE_OK; } - void WebSocket::Connect(const char* url, bool bIsReconnect, std::function fnWebsocketConnectedCallback) { - if (m_bConnected) - { + if (m_bConnected || m_bShuttingDown) return; - } - + // Copy before replacing the stored address: reconnect passes its c_str(). + const std::string destination(url); + m_strWebsocketAddr = destination; + m_fnWebsocketConnectedCallback = fnWebsocketConnectedCallback; + m_connectAsReconnect = bIsReconnect; m_lastPong = NowMs(); - - // TODO_CACHE: Cleanup multi too + m_proxySetupStarted = NowMs(); + m_proxyResolution.reset(); + m_proxyPolicy = {}; + m_proxyRoute = 0; + m_proxyPrompted = false; if (m_pCurlWS != nullptr) { - // remove from multi before cleanup (required by libcurl) - if (m_pMulti != nullptr) - { - curl_multi_remove_handle(m_pMulti, m_pCurlWS); - } - // cleanup - curl_easy_cleanup(m_pCurlWS); - m_pCurlWS = nullptr; + curl_multi_remove_handle(m_pMulti, m_pCurlWS); + curl_easy_cleanup(m_pCurlWS); + m_pCurlWS = nullptr; } - - // Free old headers before creating new ones - if (m_pHeaders != nullptr) + curl_slist_free_all(m_pHeaders); + m_pHeaders = nullptr; + if (!NGMP_OnlineServicesManager::IsWebSocketURLTrusted(destination)) { - curl_slist_free_all(m_pHeaders); - m_pHeaders = nullptr; + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Refused untrusted service destination"); + FailConnectionSetup(); + return; } + m_proxyResolution = ServiceConnection::StartProxyResolution( + NGMP_OnlineServicesManager::Settings.Network_GetConnectionSettings(), destination); +} - m_pCurlWS = curl_easy_init(); - - if (m_pCurlWS != nullptr) +// the lobby connection is a visible flow: a proxy that wants a login gets one prompt, then the handshake runs again +void WebSocket::ResumeAfterProxyLogin(bool loggedIn) +{ + if (m_bShuttingDown || m_bConnected) { - m_fnWebsocketConnectedCallback = fnWebsocketConnectedCallback; - - int httpResponseCode = -1; - m_strWebsocketAddr = std::string(url); - curl_easy_setopt(m_pCurlWS, CURLOPT_URL, url); - - curl_easy_getinfo(m_pCurlWS, CURLINFO_RESPONSE_CODE, &httpResponseCode); - - curl_easy_setopt(m_pCurlWS, CURLOPT_CONNECT_ONLY, 2L); /* websocket style */ - - // HTTP v1 seems to have a higher success rate of bypassing DPI - curl_easy_setopt(m_pCurlWS, CURLOPT_HTTP_VERSION, NGMP_OnlineServicesManager::Settings.Network_GetHTTPVersionForCurl()); - -#if _DEBUG - curl_easy_setopt(m_pCurlWS, CURLOPT_SSL_VERIFYPEER, 0); - curl_easy_setopt(m_pCurlWS, CURLOPT_SSL_VERIFYHOST, 0); - - curl_easy_setopt(m_pCurlWS, CURLOPT_VERBOSE, 1L); -#else - // Use the OS cert store (needed since we build curl against OpenSSL, not schannel). - curl_easy_setopt(m_pCurlWS, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); - curl_easy_setopt(m_pCurlWS, CURLOPT_SSL_VERIFYPEER, 1L); - curl_easy_setopt(m_pCurlWS, CURLOPT_SSL_VERIFYHOST, 2L); -#endif + return; + } + if (!loggedIn) + { + FailConnectionSetup(); + return; + } + m_proxySetupStarted = NowMs(); + StartResolvedConnection(); +} +void WebSocket::FailConnectionSetup() +{ + m_proxyResolution.reset(); + m_bConnected = false; + m_vecWSPartialBuffer.clear(); + if (m_bReconnecting) + { + m_reconnectAttemptStarted = -1; + m_nextReconnectAttempt = NowMs() + ComputeReconnectDelay(); + } + else + { + NGMP_OnlineServicesManager::GetInstance()->SetPendingFullTeardown(EGOTearDownReason::LOST_CONNECTION); + } +} - // ws needs auth - NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pAuthInterface == nullptr) +void WebSocket::PollConnectionSetup() +{ + if (!m_proxyResolution) + return; + if (NowMs() - m_proxySetupStarted >= m_reconnectAttemptTimeout) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Proxy policy failed: phase=proxy-timeout native_code=0"); + FailConnectionSetup(); + return; + } + if (!m_proxyResolution->ready.load(std::memory_order_acquire)) + return; + m_proxyPolicy = std::move(m_proxyResolution->result); + m_proxyResolution.reset(); + if (!m_proxyPolicy.error.empty() || m_proxyPolicy.routes.empty()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Proxy policy failed: phase=%s native_code=%lu detail=%s", + m_proxyPolicy.failurePhase.empty() ? "proxy-policy" : m_proxyPolicy.failurePhase.c_str(), + m_proxyPolicy.nativeError, m_proxyPolicy.error.c_str()); + FailConnectionSetup(); + return; + } + if (ServiceConnection::ShouldLogProxyPolicy(true, m_proxyPolicy)) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Proxy route policy: %zu route(s), first=%s", + m_proxyPolicy.routes.size(), m_proxyPolicy.routes.front().direct ? "direct" : m_proxyPolicy.routes.front().url.c_str()); + if (!m_proxyPolicy.fallbackReason.empty()) { - curl_easy_cleanup(m_pCurlWS); - m_pCurlWS = nullptr; - return; + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Proxy policy fallback: native_code=%lu detail=%s", + m_proxyPolicy.nativeError, m_proxyPolicy.fallbackReason.c_str()); } + } + StartResolvedConnection(); +} - char szHeaderBuffer[8192] = { 0 }; - sprintf_s(szHeaderBuffer, "Authorization: Bearer %s", pAuthInterface->GetAuthToken().c_str()); - m_pHeaders = curl_slist_append(m_pHeaders, szHeaderBuffer); - - sprintf_s(szHeaderBuffer, "is-reconnect: %s", bIsReconnect ? "true": "false"); - m_pHeaders = curl_slist_append(m_pHeaders, szHeaderBuffer); - - curl_easy_setopt(m_pCurlWS, CURLOPT_HTTPHEADER, m_pHeaders); - - //curl_easy_setopt(m_pCurl, CURLOPT_TIMEOUT_MS, 1000); - - /* Perform the request, res gets the return code */ - //CURLcode res = curl_easy_perform(m_pCurl); - curl_multi_add_handle(m_pMulti, m_pCurlWS); +void WebSocket::StartResolvedConnection() +{ + if (m_pCurlWS != nullptr) + { + curl_multi_remove_handle(m_pMulti, m_pCurlWS); + curl_easy_cleanup(m_pCurlWS); + } + curl_slist_free_all(m_pHeaders); + m_pHeaders = nullptr; + m_pCurlWS = curl_easy_init(); + if (m_proxyRoute > 0) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Route %zu: %s", m_proxyRoute + 1, m_proxyPolicy.routes[m_proxyRoute].direct ? "direct" : m_proxyPolicy.routes[m_proxyRoute].url.c_str()); + } + std::string error; + auto* auth = NGMP_OnlineServicesManager::GetInterface(); + if (m_pCurlWS == nullptr || auth == nullptr || !auth->IsLoggedIn() || !NGMP_OnlineServicesManager::IsWebSocketURLTrusted(m_strWebsocketAddr) + || !ServiceConnection::ConfigureCurl(m_pCurlWS, m_strWebsocketAddr, m_proxyPolicy.routes[m_proxyRoute], error)) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Connection policy: %s", error.c_str()); + FailConnectionSetup(); + return; } + const std::string authorization = "Authorization: Bearer " + auth->GetAuthToken(); + m_pHeaders = curl_slist_append(nullptr, authorization.c_str()); + if (m_pHeaders == nullptr) + { + FailConnectionSetup(); + return; + } + curl_slist* updated = curl_slist_append(m_pHeaders, m_connectAsReconnect ? "is-reconnect: true" : "is-reconnect: false"); + if (updated == nullptr) + { + FailConnectionSetup(); + return; + } + m_pHeaders = updated; + CURLcode result = CURLE_OK; + auto option = [&](CURLoption key, auto value) { + if (result == CURLE_OK) + result = curl_easy_setopt(m_pCurlWS, key, value); + }; + option(CURLOPT_URL, m_strWebsocketAddr.c_str()); + option(CURLOPT_CONNECT_ONLY, 2L); + option(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + const long remaining = static_cast(std::max(1, m_reconnectAttemptTimeout - (NowMs() - m_proxySetupStarted))); + option(CURLOPT_CONNECTTIMEOUT_MS, remaining); + option(CURLOPT_TIMEOUT_MS, remaining); + option(CURLOPT_HTTPHEADER, m_pHeaders); + if (result != CURLE_OK || curl_multi_add_handle(m_pMulti, m_pCurlWS) != CURLM_OK) + FailConnectionSetup(); } void WebSocket::SendData_RoomChatMessage(UnicodeString& msg, bool bIsAction) @@ -275,34 +343,24 @@ void WebSocket::SendData_JoinNetworkRoom(int roomID, uint64_t requestID) void WebSocket::Disconnect() { + m_proxyResolution.reset(); + EndReconnect(); if (!m_bConnected) { + ClearOutbound(); return; } if (m_pCurlWS != nullptr) { - // best-effort flush of anything queued since the last Tick() before closing - std::vector outboundBatch; + // best effort, never insert CLOSE into a partially sent frame + const CURLcode result = FlushOutbound(); + if (result == CURLE_OK) { - std::scoped_lock lock(m_outboundQueueMutex); - outboundBatch.swap(m_vecQueuedOutboungMsgs); - } - - for (std::string& strPayload : outboundBatch) - { - size_t sentPayload; - CURLcode sendResult = curl_ws_send(m_pCurlWS, strPayload.c_str(), strPayload.length(), &sentPayload, 0, CURLWS_BINARY); - if (sendResult != CURLE_OK) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Disconnect: failed to flush queued message: %s", curl_easy_strerror(sendResult)); - } + size_t sent = 0; + (void)curl_ws_send(m_pCurlWS, "", 0, &sent, 0, CURLWS_CLOSE); } - // send close - size_t sent; - (void)curl_ws_send(m_pCurlWS, "", 0, &sent, 0, CURLWS_CLOSE); - // release headers if (m_pHeaders != nullptr) { @@ -323,23 +381,104 @@ void WebSocket::Disconnect() m_pCurlWS = nullptr; } + ClearOutbound(); m_vecWSPartialBuffer.clear(); m_bConnected = false; } void WebSocket::Send(const char* send_payload) { - // Thread-safe; always queues. Tick() flushes on the main thread. + QueueOutbound(std::string(send_payload), false); +} + +void WebSocket::QueueOutbound(std::string payload, bool heartbeat) +{ std::scoped_lock lock(m_outboundQueueMutex); + if (m_bShuttingDown || !m_acceptOutbound) + { + if (!heartbeat && !m_bShuttingDown) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Rejecting message while disconnected; actions are not replayed after reconnect"); + } + return; + } + if (heartbeat && m_heartbeatQueued) + { + return; + } static constexpr size_t kMaxQueuedOutboundMsgs = 256; - if (m_vecQueuedOutboungMsgs.size() >= kMaxQueuedOutboundMsgs) + static constexpr size_t kMaxQueuedOutboundBytes = 2 * 1024 * 1024; + if (m_vecQueuedOutboungMsgs.size() >= kMaxQueuedOutboundMsgs || + payload.size() > kMaxQueuedOutboundBytes - m_queuedOutboundBytes) { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Outbound queue full (%zu), discarding oldest message", m_vecQueuedOutboungMsgs.size()); - m_vecQueuedOutboungMsgs.erase(m_vecQueuedOutboungMsgs.begin()); + // the front may be partially transmitted, do not evict it + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Outbound queue full (%zu messages, %zu bytes), rejecting new message", m_vecQueuedOutboungMsgs.size(), m_queuedOutboundBytes); + return; } - m_vecQueuedOutboungMsgs.push_back(std::string(send_payload)); + m_queuedOutboundBytes += payload.size(); + m_vecQueuedOutboungMsgs.push_back({ std::move(payload), heartbeat }); + m_heartbeatQueued |= heartbeat; +} + +void WebSocket::ClearOutbound() +{ + std::scoped_lock lock(m_outboundQueueMutex); + // delivery is ambiguous after a socket failure, so old actions are not replayed on a new socket + if (!m_vecQueuedOutboungMsgs.empty()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Discarding %zu messages from closed connection (%zu bytes, %zu bytes consumed from current message)", m_vecQueuedOutboungMsgs.size(), m_queuedOutboundBytes, m_outboundOffset); + } + m_vecQueuedOutboungMsgs.clear(); + m_queuedOutboundBytes = 0; + m_outboundOffset = 0; + m_heartbeatQueued = false; + m_acceptOutbound = false; +} + +CURLcode WebSocket::FlushOutbound() +{ + // bounded work on the game thread, each call passes the remaining payload of the same frame (curl_ws_send partial writes) + size_t bytesSent = 0; + for (int attempt = 0; attempt < 16; ++attempt) + { + std::scoped_lock lock(m_outboundQueueMutex); + if (m_vecQueuedOutboungMsgs.empty()) + { + return CURLE_OK; + } + const OutboundMessage& message = m_vecQueuedOutboungMsgs.front(); + size_t sent = 0; + const size_t remaining = message.payload.size() - m_outboundOffset; + CURLcode result = curl_ws_send(m_pCurlWS, message.payload.data() + m_outboundOffset, + remaining, &sent, 0, CURLWS_BINARY); + if (sent > remaining) + { + return CURLE_SEND_ERROR; + } + m_outboundOffset += sent; + bytesSent += sent; + if (result != CURLE_OK && result != CURLE_AGAIN) + { + return result; + } + if (m_outboundOffset == message.payload.size()) + { + m_queuedOutboundBytes -= message.payload.size(); + if (message.heartbeat) + { + m_heartbeatQueued = false; + } + m_vecQueuedOutboungMsgs.erase(m_vecQueuedOutboungMsgs.begin()); + m_outboundOffset = 0; + } + if (result == CURLE_AGAIN || sent == 0 || bytesSent >= 256 * 1024) + { + return CURLE_AGAIN; + } + } + return CURLE_AGAIN; } class WebSocketMessageBase @@ -542,12 +681,26 @@ static bool JSONDeserialize(const char* szBuffer, nlohmann::json* jsonObject) } catch (nlohmann::json::exception& jsonException) { - NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONDeserialize: Unparsable JSON: %s (%s)", szBuffer, jsonException.what()); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONDeserialize: Unparsable JSON: %s (%s)", szBuffer, jsonException.what()); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONDeserialize: Invalid message JSON"); + } return false; } catch (...) { - NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONDeserialize: Unparsable JSON: %s", szBuffer); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONDeserialize: Unparsable JSON: %s", szBuffer); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONDeserialize: Invalid message JSON"); + } return false; } @@ -566,7 +719,14 @@ static bool JSONGetAsObject(nlohmann::json& jsonObject, T* outMsg) catch (nlohmann::json::exception& jsonException) { std::string targetTypeName = typeid(T).name(); - NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONGetAsObject: Unparsable JSON: Target Type is %s (%s)", targetTypeName.c_str(), jsonException.what()); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONGetAsObject: Unparsable JSON: Target Type is %s (%s)", targetTypeName.c_str(), jsonException.what()); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONGetAsObject: Invalid message type %s", targetTypeName.c_str()); + } return false; } catch (...) @@ -589,6 +749,7 @@ void WebSocket::BeginReconnect() return; } + ClearOutbound(); m_bReconnecting = true; m_numReconnectAttempts = 0; m_bFreshSession = false; @@ -748,8 +909,13 @@ void WebSocket::UpdateReconnect() void WebSocket::Tick() { + if (m_bShuttingDown || m_pMulti == nullptr) + { + return; + } // Main thread only; m_pCurlWS/m_vecWSPartialBuffer/m_bConnected are unlocked here. UpdateReconnect(); + PollConnectionSetup(); @@ -773,13 +939,7 @@ void WebSocket::Tick() pWS->SendData_Signalling(strSignal); */ - // ping? int64_t currTime = NowMs(); - if ((currTime - m_lastPing) > m_timeBetweenUserPings) - { - m_lastPing = currTime; - Ping(); - }; int numReqs = 0; curl_multi_perform(m_pMulti, &numReqs); @@ -797,15 +957,43 @@ void WebSocket::Tick() if (pCurlHandle == m_pCurlWS) // shouldnt hear about anything else { - int httpResponseCode = -1; + long httpResponseCode = 0; + long proxyStatus = 0; curl_easy_getinfo(pCurlHandle, CURLINFO_RESPONSE_CODE, &httpResponseCode); + curl_easy_getinfo(pCurlHandle, CURLINFO_HTTP_CONNECTCODE, &proxyStatus); + if (proxyStatus == 407) + httpResponseCode = 407; /* Check for errors */ if (m->data.result != CURLE_OK) { + const CURLcode failure = m->data.result; + if (httpResponseCode != 407 && (failure == CURLE_COULDNT_CONNECT || failure == CURLE_COULDNT_RESOLVE_PROXY || failure == CURLE_COULDNT_RESOLVE_HOST) + && m_proxyRoute + 1 < m_proxyPolicy.routes.size() && NowMs() - m_proxySetupStarted < m_reconnectAttemptTimeout) + { + ++m_proxyRoute; + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Trying next policy route after pre-connection failure (%d)", failure); + StartResolvedConnection(); + continue; + } m_bConnected = false; m_vecWSPartialBuffer.clear(); - NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Failed to connect (%d - %s)", m->data.result, curl_easy_strerror(m->data.result)); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Handshake failed: phase=%s curl=%d status=%ld proxy_status=%ld", + ServiceConnection::FailurePhase(m->data.result, httpResponseCode, proxyStatus), m->data.result, httpResponseCode, proxyStatus); + if (httpResponseCode == 407 && !m_bReconnecting && !m_proxyPrompted && m_proxyRoute < m_proxyPolicy.routes.size() && !m_proxyPolicy.routes[m_proxyRoute].direct) + { + m_proxyPrompted = true; + ServiceConnection::RequestProxyLogin(m_proxyPolicy.routes[m_proxyRoute], [](bool loggedIn) + { + auto* manager = NGMP_OnlineServicesManager::GetInstance(); + auto socket = manager ? manager->Internal_GetWebSocket() : nullptr; + if (socket) + { + socket->ResumeAfterProxyLogin(loggedIn); + } + }); + continue; + } if (m_bReconnecting) { @@ -843,8 +1031,16 @@ void WebSocket::Tick() NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Connected"); } + long protocol = 0; + curl_easy_getinfo(pCurlHandle, CURLINFO_HTTP_VERSION, &protocol); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Handshake status=%ld protocol=%s", httpResponseCode, ServiceConnection::ProtocolName(protocol)); + /* connected and ready */ m_bConnected = true; + { + std::scoped_lock lock(m_outboundQueueMutex); + m_acceptOutbound = true; + } m_vecWSPartialBuffer.clear(); EndReconnect(); @@ -867,42 +1063,81 @@ void WebSocket::Tick() return; } - // send anything we have queued (things sent while not connected, or from any other thread) - std::vector outboundBatch; + if ((currTime - m_lastPing) > m_timeBetweenUserPings) { - std::scoped_lock lock(m_outboundQueueMutex); - outboundBatch.swap(m_vecQueuedOutboungMsgs); + m_lastPing = currTime; + Ping(); } - for (std::string& strPayload : outboundBatch) + const CURLcode sendResult = FlushOutbound(); + if (sendResult != CURLE_OK && sendResult != CURLE_AGAIN) { - size_t sent; - CURLcode result = curl_ws_send(m_pCurlWS, strPayload.c_str(), strPayload.length(), &sent, 0, CURLWS_BINARY); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Send failed: %s, attempting reconnect", curl_easy_strerror(sendResult)); + m_bConnected = false; + m_vecWSPartialBuffer.clear(); + BeginReconnect(); + return; + } - if (result != CURLE_OK) + // drain what the server queued, a signalling burst shouldn't take one frame per tick + static constexpr int MAX_WS_FRAMES_PER_TICK = 64; + CURLcode ret = CURL_LAST; + for (int numFrames = 0; numFrames < MAX_WS_FRAMES_PER_TICK; ++numFrames) + { + ret = ReceiveOneFrame(); + if (ret != CURLE_OK || !m_bConnected) + { + break; + } + } + + // time since last pong? + if (m_bConnected && m_lastPong != -1 && (currTime - m_lastPong) >= m_timeForWSTimeout) + { + // send event to sentry +#if defined(GENERALS_ONLINE_USE_SENTRY) + if (TheNGMPGame != nullptr) { - NetworkLog(ELogVerbosity::LOG_RELEASE, "curl_ws_send() failed: %s\n", curl_easy_strerror(result)); + AsciiString sentryMsg; + sentryMsg.format("Got websocket disconnect (Timeout: %s), timeout is %lld, last pong was at %lld, current time is %lld, attempting reconnect", curl_easy_strerror(ret), currTime - m_lastPong, m_lastPong, currTime); + sentry_capture_event(sentry_value_new_message_event(SENTRY_LEVEL_ERROR, "WEBSOCKET_DISCONNECT_TIMEOUT", sentryMsg.str())); } - } +#endif + NetworkLog(ELogVerbosity::LOG_RELEASE, "Got websocket disconnect (Timeout: %s), timeout is %lld, last pong was at %lld, current time is %lld, attempting reconnect", curl_easy_strerror(ret), currTime - m_lastPong, m_lastPong, currTime); + m_bConnected = false; + BeginReconnect(); + m_vecWSPartialBuffer.clear(); + }; +} + +// Reads and handles at most one frame, returns the curl_ws_recv result (CURL_LAST if nothing was read) +CURLcode WebSocket::ReceiveOneFrame() +{ // do recv size_t rlen = 0; const struct curl_ws_frame* meta = nullptr; char bufferThisRecv[8196 * 4] = { 0 }; - CURLcode ret = CURL_LAST; - ret = curl_ws_recv(m_pCurlWS, bufferThisRecv, sizeof(bufferThisRecv), &rlen, &meta); + CURLcode ret = curl_ws_recv(m_pCurlWS, bufferThisRecv, sizeof(bufferThisRecv) - 1, &rlen, &meta); // SECURITY FIX: Validate rlen is within buffer bounds - if (rlen > sizeof(bufferThisRecv)) + if (rlen >= sizeof(bufferThisRecv)) { NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Received data size %zu exceeds buffer size %zu, discarding", rlen, sizeof(bufferThisRecv)); - return; + return CURL_LAST; } - if (ret != CURLE_RECV_ERROR && ret != CURL_LAST && ret != CURLE_AGAIN && ret != CURLE_GOT_NOTHING) + if (ret == CURLE_OK) { - NetworkLog(ELogVerbosity::LOG_DEBUG, "Got websocket msg: %s", bufferThisRecv); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_DEBUG, "Got websocket msg: %s", bufferThisRecv); + } + else + { + NetworkLog(ELogVerbosity::LOG_DEBUG, "[WebSocket] Received %zu bytes", rlen); + } NetworkLog(ELogVerbosity::LOG_DEBUG, "Got websocket len: %d", rlen); // any server frame proves liveness, not just a JSON PONG @@ -925,7 +1160,7 @@ void WebSocket::Tick() { NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Partial buffer overflow, discarding message"); m_vecWSPartialBuffer.clear(); - return; + return CURL_LAST; } // SECURITY FIX: Store old size BEFORE resize to avoid off-by-one error in memcpy @@ -1535,7 +1770,14 @@ void WebSocket::Tick() if (bParsed) { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[PROBE] GOT PROBE REQUEST: %s!", probe.url.c_str()); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[PROBE] GOT PROBE REQUEST: %s!", probe.url.c_str()); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[PROBE] Received request"); + } NGMP_OnlineServicesManager::GetInstance()->CaptureScreenshotForProbe(EScreenshotType::SCREENSHOT_TYPE_GAMEPLAY, probe.url); @@ -1724,21 +1966,41 @@ void WebSocket::Tick() } catch (nlohmann::json::exception& jsonException) { - - NetworkLog(ELogVerbosity::LOG_RELEASE, "Unparsable WebSocketMessage 101: %s (JSON: %s)", bufferThisRecv, jsonException.what()); - NetworkLog(ELogVerbosity::LOG_RELEASE, "Buildup buffer is: %s", m_vecWSPartialBuffer.data()); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Unparsable WebSocketMessage 101: %s (JSON: %s)", bufferThisRecv, jsonException.what()); + NetworkLog(ELogVerbosity::LOG_RELEASE, "Buildup buffer is: %s", m_vecWSPartialBuffer.data()); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Invalid JSON message"); + } m_vecWSPartialBuffer.clear(); } catch (std::exception& e) { - NetworkLog(ELogVerbosity::LOG_RELEASE, "Unparsable WebSocketMessage 100: %s (%s)", bufferThisRecv, e.what()); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Unparsable WebSocketMessage 100: %s (%s)", bufferThisRecv, e.what()); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Message processing failed"); + } m_vecWSPartialBuffer.clear(); } catch (...) { - NetworkLog(ELogVerbosity::LOG_RELEASE, "Unparsable WebSocketMessage 102: %s", bufferThisRecv); + if (NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Unparsable WebSocketMessage 102: %s", bufferThisRecv); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Message processing failed"); + } m_vecWSPartialBuffer.clear(); } @@ -1774,7 +2036,7 @@ void WebSocket::Tick() NetworkLog(ELogVerbosity::LOG_DEBUG, "websocket meta was null"); } } - else if (ret == CURLE_RECV_ERROR) + else if (ret != CURLE_AGAIN) { NetworkLog(ELogVerbosity::LOG_RELEASE, "Got websocket disconnect (ERROR: %s), Attempting reconnect", curl_easy_strerror(ret)); @@ -1795,24 +2057,7 @@ void WebSocket::Tick() #endif } - // time since last pong? - if (m_lastPong != -1 && (currTime - m_lastPong) >= m_timeForWSTimeout) - { - // send event to sentry -#if defined(GENERALS_ONLINE_USE_SENTRY) - if (TheNGMPGame != nullptr) - { - AsciiString sentryMsg; - sentryMsg.format("Got websocket disconnect (Timeout: %s), timeout is %lld, last pong was at %lld, current time is %lld, attempting reconnect", curl_easy_strerror(ret), currTime - m_lastPong, m_lastPong, currTime); - sentry_capture_event(sentry_value_new_message_event(SENTRY_LEVEL_ERROR, "WEBSOCKET_DISCONNECT_TIMEOUT", sentryMsg.str())); - } -#endif - - NetworkLog(ELogVerbosity::LOG_RELEASE, "Got websocket disconnect (Timeout: %s), timeout is %lld, last pong was at %lld, current time is %lld, attempting reconnect", curl_easy_strerror(ret), currTime - m_lastPong, m_lastPong, currTime); - m_bConnected = false; - BeginReconnect(); - m_vecWSPartialBuffer.clear(); - }; + return ret; } NGMP_OnlineServices_RoomsInterface::NGMP_OnlineServices_RoomsInterface() diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/ProxyLogin.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/ProxyLogin.cpp new file mode 100644 index 00000000000..bc7114616d4 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/ProxyLogin.cpp @@ -0,0 +1,597 @@ +#include "PreRTS.h" + +#include "GameNetwork/GeneralsOnline/ProxyLogin.h" +#include "GameNetwork/GeneralsOnline/ServiceConnectionPolicy.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_Init.h" +#include "Common/NameKeyGenerator.h" +#include "GameClient/GameText.h" +#include "GameClient/GameWindowManager.h" +#include "GameClient/WindowLayout.h" +#include "GameClient/Gadget.h" +#include "GameClient/GadgetCheckBox.h" +#include "GameClient/GadgetPushButton.h" +#include "GameClient/GadgetStaticText.h" +#include "GameClient/GadgetTextEntry.h" +#include "GameClient/DisplayString.h" +#include "GameClient/DisplayStringManager.h" +#include "GameClient/KeyDefs.h" +#include +#include +#include +#include +#include + +// One dialog for the login of a proxy and the connection test, laid out in Menus/ProxyLogin.wnd in the frame of the game's message box. +// States: testing, form, checking, result. The form offers the Windows login where the proxy can use it (ticked when the silent one was not accepted) or typed credentials. +namespace ServiceConnection +{ +namespace +{ +std::mutex mainThreadMutex; +std::deque> mainThreadTasks; + +enum class State { Testing, Form, Checking, Result }; + +struct Dialog +{ + WindowLayout *layout = nullptr; + GameWindow *parent = nullptr; + GameWindow *title = nullptr; + GameWindow *message = nullptr; + GameWindow *proxyLabel = nullptr; + GameWindow *proxy = nullptr; + GameWindow *userName = nullptr; + GameWindow *userLabel = nullptr; + GameWindow *password = nullptr; + GameWindow *passwordLabel = nullptr; + GameWindow *windows = nullptr; + GameWindow *remember = nullptr; + GameWindow *ok = nullptr; + GameWindow *save = nullptr; + GameWindow *cancel = nullptr; + GameWindow *previousFocus = nullptr; + std::vector coveredBoxes; + State state = State::Testing; + bool testPath = false; + bool acceptMode = false; + bool failed = false; + bool windowsLoginPossible = true; + unsigned generation = 0; + std::string proxyURL; + ServiceConnectionSettings settings; + std::string pendingUser; + std::string pendingPassword; + bool pendingRemember = false; + std::function done; + std::function decided; +}; + +Dialog dialog; + +const char *environmentName() +{ + return NGMP_OnlineServicesManager::g_Environment == NGMP_OnlineServicesManager::EEnvironment::DEV ? "dev" : + NGMP_OnlineServicesManager::g_Environment == NGMP_OnlineServicesManager::EEnvironment::TEST ? "test" : "prod"; +} + +GameWindow *control(const char *name) +{ + return TheWindowManager->winGetWindowFromId(nullptr, TheNameKeyGenerator->nameToKey(name)); +} + +UnicodeString wideText(const std::string &text) +{ + UnicodeString result; + result.translate(text.c_str()); + return result; +} + +std::string narrowText(const UnicodeString &text) +{ + AsciiString ascii; + ascii.translate(text); + return ascii.str(); +} + +void wipeString(std::string &value) +{ + if (!value.empty()) + { + SecureZeroMemory(value.data(), value.size()); + } + value.clear(); +} + +std::string upperText(std::string text) +{ + std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return (char)toupper(c); }); + return text; +} + +Int measure(GameFont *font, const std::string &text) +{ + DisplayString *string = TheDisplayStringManager->newDisplayString(); + string->setFont(font); + string->setText(wideText(text)); + const Int width = string->getWidth(); + TheDisplayStringManager->freeDisplayString(string); + return width; +} + +void updateFormEnabled() +{ + const bool editable = dialog.state == State::Form; + // with the Windows login the typed credentials are not used, so their fields are greyed + const bool typed = editable && GadgetCheckBoxIsChecked(dialog.windows) == FALSE; + dialog.windows->winEnable(editable && dialog.windowsLoginPossible); + dialog.userName->winEnable(typed); + dialog.password->winEnable(typed); + dialog.remember->winEnable(typed); + dialog.ok->winEnable(dialog.state == State::Form || dialog.state == State::Result); + // after a plain result OK is the only way out + dialog.cancel->winEnable(dialog.state != State::Result || (dialog.acceptMode && dialog.failed)); +} + +// a label that fits the button, otherwise the short one +std::string fittingLabel(GameWindow *button, const std::string &label, const std::string &shortLabel) +{ + Int width = 0; + Int height = 0; + button->winGetSize(&width, &height); + return measure(button->winGetFont(), label) + TheWindowManager->winFontHeight(button->winGetFont()) <= width ? label : shortLabel; +} + +void setState(State state, const std::string &title, const UnicodeString &message, bool isError = false) +{ + dialog.state = state; + const bool form = state == State::Form || state == State::Checking; + const bool failedAccept = state == State::Result && dialog.acceptMode && dialog.failed; + GadgetButtonSetText(dialog.ok, TheGameText->fetch(failedAccept ? "GUI:Back" : "GUI:Ok")); + GadgetButtonSetText(dialog.save, wideText(fittingLabel(dialog.save, "SAVE ANYWAY", "SAVE"))); + + GadgetStaticTextSetText(dialog.title, wideText(upperText(title))); + GadgetStaticTextSetText(dialog.message, message); + dialog.message->winSetEnabledTextColors(isError ? GameMakeColor(255, 96, 96, 255) : GameMakeColor(255, 255, 255, 255), GameMakeColor(0, 0, 0, 255)); + for (GameWindow *window : { dialog.proxyLabel, dialog.proxy, dialog.windows, dialog.userLabel, dialog.userName, dialog.passwordLabel, dialog.password, dialog.remember }) + { + window->winHide(!form); + } + dialog.save->winHide(!failedAccept); + updateFormEnabled(); +} + +// A modal message box (such as the update check) would stay above the dialog, so those boxes are hidden while it is open +void hideMessageBoxes() +{ + const NameKeyType boxKey = TheNameKeyGenerator->nameToKey("MessageBox.wnd:MessageBoxParent"); + for (GameWindow *top = TheWindowManager->winGetWindowList(); top != nullptr; top = top->winGetNext()) + { + if (top != dialog.parent && !top->winIsHidden() && TheWindowManager->winGetWindowFromId(top, boxKey) != nullptr) + { + dialog.coveredBoxes.push_back(top); + top->winHide(TRUE); + } + } +} + +// only boxes that are still open get shown again +void showMessageBoxes(const std::vector &boxes) +{ + for (GameWindow *top = TheWindowManager->winGetWindowList(); top != nullptr; top = top->winGetNext()) + { + if (std::find(boxes.begin(), boxes.end(), top) != boxes.end()) + { + top->winHide(FALSE); + } + } +} + +// ---- results --------------------------------------------------------------------------------------------------------------------- + +void finishDialog(bool loggedIn, AcceptChoice choice = AcceptChoice::KeepEditing) +{ + std::function done = std::move(dialog.done); + std::function decided = std::move(dialog.decided); + GameWindow *previousFocus = dialog.previousFocus; + std::vector coveredBoxes = std::move(dialog.coveredBoxes); + TheWindowManager->winUnsetModal(dialog.parent); + GadgetTextEntrySetText(dialog.password, UnicodeString::TheEmptyString); + wipeString(dialog.pendingPassword); + dialog.layout->destroyWindows(); + deleteInstance(dialog.layout); + const unsigned generation = dialog.generation + 1; + dialog = Dialog(); + dialog.generation = generation; + showMessageBoxes(coveredBoxes); + if (previousFocus != nullptr) + { + TheWindowManager->winSetFocus(previousFocus); + } + if (done) + { + done(loggedIn); + } + if (decided) + { + decided(choice); + } +} + +// the proxy accepted the login: keep it as the player chose +void commitLogin() +{ + std::string error; + if (dialog.pendingRemember) + { + ClearSessionProxyCredentials(dialog.proxyURL); + if (!SaveProxyCredentials(dialog.proxyURL, dialog.pendingUser, dialog.pendingPassword, error)) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[Connection] Unable to save the proxy login: %s", error.c_str()); + SetSessionProxyCredentials(dialog.proxyURL, dialog.pendingUser, dialog.pendingPassword); + } + } + wipeString(dialog.pendingPassword); +} + +void showForm(const UnicodeString &errorText, bool clearPassword) +{ + GadgetTextEntrySetText(dialog.proxy, wideText(ProxyDisplayName(dialog.proxyURL))); + setState(State::Form, "Proxy login", errorText, true); + if (clearPassword) + { + GadgetTextEntrySetText(dialog.password, UnicodeString::TheEmptyString); + } + TheWindowManager->winSetFocus(GadgetCheckBoxIsChecked(dialog.windows) ? dialog.ok : narrowText(GadgetTextEntryGetText(dialog.userName)).empty() ? dialog.userName : dialog.password); +} + +void startProbe(ServiceConnectionSettings settings) +{ + const unsigned generation = ++dialog.generation; + const std::string environment = environmentName(); + std::thread([generation, settings, environment]() + { + ConnectionTestResult result; + try + { + result = TestServiceConnection(settings, environment, 12); + } + catch (...) + { + } + RunOnMainThread([generation, result]() + { + if (dialog.layout == nullptr || dialog.generation != generation) + { + return; + } + using Outcome = ConnectionTestResult::Outcome; + if (dialog.state == State::Testing) + { + if (result.outcome == Outcome::ProxyAuthRequired && !result.proxyURL.empty()) + { + dialog.proxyURL = result.proxyURL; + dialog.windowsLoginPossible = result.proxyIntegratedAuthPossible; + if (!dialog.windowsLoginPossible) + { + GadgetCheckBoxSetChecked(dialog.windows, FALSE); + } + const std::string savedUser = ProxyUsername(result.proxyURL); + GadgetTextEntrySetText(dialog.userName, wideText(savedUser)); + GadgetCheckBoxSetChecked(dialog.remember, HasProxyCredentials(result.proxyURL)); + showForm(result.detail == "refused" ? TheGameText->fetch("GUI:GSDisconReason8") : UnicodeString::TheEmptyString, false); + } + else if (dialog.acceptMode && result.outcome == Outcome::Connected) + { + finishDialog(true, AcceptChoice::Save); + } + else + { + dialog.failed = result.outcome != Outcome::Connected; + setState(State::Result, "Connection test", wideText(DescribeConnectionTest(result))); + } + return; + } + // checking a login + if (result.outcome == Outcome::ProxyAuthRequired) + { + ClearSessionProxyCredentials(dialog.proxyURL); + wipeString(dialog.pendingPassword); + showForm(GadgetCheckBoxIsChecked(dialog.windows) ? wideText("The Windows login was not accepted.") : TheGameText->fetch("GUI:GSDisconReason8"), true); + return; + } + if (result.outcome == Outcome::Unreachable && !result.proxyAnswered) + { + ClearSessionProxyCredentials(dialog.proxyURL); + wipeString(dialog.pendingPassword); + showForm(TheGameText->fetch("GUI:ConnectionFailed"), false); + return; + } + commitLogin(); + if (!dialog.testPath) + { + finishDialog(true); + return; + } + if (dialog.acceptMode && result.outcome == Outcome::Connected) + { + finishDialog(true, AcceptChoice::Save); + return; + } + dialog.failed = result.outcome != Outcome::Connected; + setState(State::Result, "Connection test", + wideText(result.outcome == Outcome::Connected ? DescribeConnectionTest(result) : + result.outcome == Outcome::Unreachable ? "Logged in to the proxy, but could not reach the service." : + "Logged in to the proxy. " + DescribeConnectionTest(result))); + }); + }).detach(); +} + +// switches to the checking state; stays on the form when the input is incomplete +void acceptForm() +{ + const bool windowsLogin = GadgetCheckBoxIsChecked(dialog.windows) != FALSE; + // the choice is kept, and the check below uses it; a proxy that cannot use the Windows login leaves it as it was + if (dialog.windowsLoginPossible) + { + NGMP_OnlineServicesManager::Settings.Network_SaveWindowsLogin(windowsLogin); + dialog.settings.windowsLogin = windowsLogin; + } + if (windowsLogin) + { + ClearSessionProxyCredentials(dialog.proxyURL); + dialog.pendingUser.clear(); + wipeString(dialog.pendingPassword); + dialog.pendingRemember = false; + setState(State::Checking, "Proxy login", TheGameText->fetch("WOL:Connecting")); + startProbe(dialog.testPath ? dialog.settings : NGMP_OnlineServicesManager::Settings.Network_GetConnectionSettings()); + return; + } + std::string user = narrowText(GadgetTextEntryGetText(dialog.userName)); + std::string password = narrowText(GadgetTextEntryGetText(dialog.password)); + if (user.empty()) + { + showForm(wideText("Enter the user name."), false); + wipeString(password); + return; + } + dialog.pendingUser = user; + dialog.pendingPassword = password; + dialog.pendingRemember = GadgetCheckBoxIsChecked(dialog.remember) != FALSE; + wipeString(password); + GadgetTextEntrySetText(dialog.password, UnicodeString::TheEmptyString); + + // the login is used for the check first; it is kept once the proxy accepted it + SetSessionProxyCredentials(dialog.proxyURL, dialog.pendingUser, dialog.pendingPassword); + setState(State::Checking, "Proxy login", TheGameText->fetch("WOL:Connecting")); + startProbe(dialog.testPath ? dialog.settings : NGMP_OnlineServicesManager::Settings.Network_GetConnectionSettings()); +} + +void cancelDialog() +{ + if (dialog.state == State::Checking) + { + ClearSessionProxyCredentials(dialog.proxyURL); + } + // Cancel after a failed check drops the connection changes, earlier it only stops the check + finishDialog(false, dialog.acceptMode && dialog.state == State::Result ? AcceptChoice::Discard : AcceptChoice::KeepEditing); +} + +WindowMsgHandledType dialogSystem(GameWindow *window, UnsignedInt msg, WindowMsgData mData1, WindowMsgData mData2) +{ + if (dialog.layout == nullptr) + { + return MSG_IGNORED; + } + switch (msg) + { + case GWM_INPUT_FOCUS: + { + if (mData1 == TRUE) + { + *(Bool *)mData2 = TRUE; + } + return MSG_HANDLED; + } + case GBM_SELECTED: + { + GameWindow *source = (GameWindow *)mData1; + if (source == dialog.ok) + { + if (dialog.state == State::Form) + { + acceptForm(); + } + else if (dialog.state == State::Result) + { + // with a failed check before Accept this button is "Keep editing" + finishDialog(!dialog.acceptMode, AcceptChoice::KeepEditing); + } + } + else if (source == dialog.windows) + { + updateFormEnabled(); + } + else if (source == dialog.save) + { + finishDialog(true, AcceptChoice::Save); + } + else if (source == dialog.cancel) + { + cancelDialog(); + } + return MSG_HANDLED; + } + case GEM_EDIT_DONE: + { + GameWindow *source = (GameWindow *)mData1; + if (dialog.state != State::Form) + { + return MSG_HANDLED; + } + if (source == dialog.userName) + { + TheWindowManager->winSetFocus(dialog.password); + } + else if (source == dialog.password) + { + acceptForm(); + } + return MSG_HANDLED; + } + } + return MSG_IGNORED; +} + +WindowMsgHandledType dialogInput(GameWindow *window, UnsignedInt msg, WindowMsgData mData1, WindowMsgData mData2) +{ + if (dialog.layout == nullptr) + { + return MSG_IGNORED; + } + if (msg == GWM_CHAR) + { + if ((UnsignedByte)mData1 == KEY_ESC) + { + if (BitIsSet((UnsignedByte)mData2, KEY_STATE_UP)) + { + cancelDialog(); + } + return MSG_HANDLED; + } + } + return MSG_IGNORED; +} + +// builds the dialog; returns false when the layout could not be built +bool openDialog(bool testPath) +{ + if (dialog.layout != nullptr || TheWindowManager == nullptr) + { + return false; + } + WindowLayout *layout = TheWindowManager->winCreateLayout(AsciiString("Menus/ProxyLogin.wnd")); + if (layout == nullptr) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[Connection] The proxy login layout Menus/ProxyLogin.wnd could not be loaded"); + return false; + } + Dialog d; + d.generation = dialog.generation; + d.layout = layout; + d.parent = control("ProxyLogin.wnd:ProxyLoginRoot"); + d.title = control("ProxyLogin.wnd:StaticTextTitle"); + d.message = control("ProxyLogin.wnd:StaticTextMessage"); + d.proxyLabel = control("ProxyLogin.wnd:StaticTextProxy"); + d.proxy = control("ProxyLogin.wnd:TextEntryProxy"); + d.userLabel = control("ProxyLogin.wnd:StaticTextUserName"); + d.userName = control("ProxyLogin.wnd:TextEntryUserName"); + d.passwordLabel = control("ProxyLogin.wnd:StaticTextPassword"); + d.password = control("ProxyLogin.wnd:TextEntryPassword"); + d.windows = control("ProxyLogin.wnd:CheckBoxWindowsLogin"); + d.remember = control("ProxyLogin.wnd:CheckBoxRemember"); + d.ok = control("ProxyLogin.wnd:ButtonOk"); + d.save = control("ProxyLogin.wnd:ButtonSave"); + d.cancel = control("ProxyLogin.wnd:ButtonCancel"); + for (GameWindow *window : { d.parent, d.title, d.message, d.proxyLabel, d.proxy, d.userLabel, d.userName, d.passwordLabel, d.password, d.windows, d.remember, d.ok, d.save, d.cancel }) + { + if (window == nullptr) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[Connection] The proxy login layout Menus/ProxyLogin.wnd is missing a control"); + layout->destroyWindows(); + deleteInstance(layout); + return false; + } + } + d.testPath = testPath; + d.previousFocus = TheWindowManager->winGetFocus(); + dialog = std::move(d); + + GadgetStaticTextSetText(dialog.proxyLabel, UnicodeString(L"Proxy")); + GadgetStaticTextSetText(dialog.userLabel, UnicodeString(L"Username")); + dialog.proxy->winEnable(FALSE); + GadgetStaticTextSetText(dialog.passwordLabel, TheGameText->fetch("GUI:Password")); + GadgetCheckBoxSetText(dialog.remember, TheGameText->fetch("GUI:RememberPassword")); + GadgetCheckBoxSetText(dialog.windows, UnicodeString(L"Use Windows login")); + dialog.windows->winSetTooltip(UnicodeString(L"Log in to the proxy with the Windows account of this PC. Untick it to type a user name and password instead.")); + GadgetButtonSetText(dialog.ok, TheGameText->fetch("GUI:Ok")); + GadgetButtonSetText(dialog.cancel, TheGameText->fetch("GUI:Cancel")); + dialog.userName->winSetTooltip(UnicodeString(L"The proxy user name, often DOMAIN\\name.")); + dialog.password->winSetTooltip(UnicodeString(L"The proxy password.")); + dialog.remember->winSetTooltip(UnicodeString(L"Save this login for next time on this PC. Otherwise it is only used until the game closes.")); + GadgetCheckBoxSetChecked(dialog.remember, FALSE); + GadgetTextEntrySetText(dialog.password, UnicodeString::TheEmptyString); + + dialog.parent->winSetSystemFunc(dialogSystem); + dialog.parent->winSetInputFunc(dialogInput); + return true; +} + +void showDialog() +{ + hideMessageBoxes(); + dialog.layout->hide(FALSE); + dialog.layout->bringForward(); + dialog.parent->winBringToTop(); + TheWindowManager->winSetModal(dialog.parent); + TheWindowManager->winSetFocus(dialog.state == State::Form ? dialog.userName : dialog.cancel); +} +} + +void RunOnMainThread(std::function task) +{ + std::lock_guard lock(mainThreadMutex); + mainThreadTasks.push_back(std::move(task)); +} + +void PumpMainThreadTasks() +{ + std::deque> tasks; + { + std::lock_guard lock(mainThreadMutex); + tasks.swap(mainThreadTasks); + } + for (auto &task : tasks) + { + task(); + } +} + +bool IsProxyLoginOpen() +{ + return dialog.layout != nullptr; +} + +void RequestProxyLogin(const ProxyRoute &route, std::function done) +{ + if (!openDialog(false)) + { + done(false); + return; + } + dialog.proxyURL = route.url; + dialog.windowsLoginPossible = route.integratedAuthPossible; + dialog.done = std::move(done); + GadgetTextEntrySetText(dialog.userName, wideText(ProxyUsername(route.url))); + GadgetCheckBoxSetChecked(dialog.remember, HasProxyCredentials(route.url)); + GadgetCheckBoxSetChecked(dialog.windows, route.integratedAuthPossible && (route.allowIntegratedAuth || NGMP_OnlineServicesManager::Settings.Network_GetConnectionSettings().windowsLogin)); + showForm(route.allowIntegratedAuth ? wideText("The Windows login was not accepted.") : UnicodeString::TheEmptyString, true); + showDialog(); +} + +// Accept in Options: tests the new settings first and reports what to do with them +void ShowAcceptTest(const ServiceConnectionSettings &settings, std::function decided) +{ + if (!openDialog(true)) + { + decided(AcceptChoice::Save); + return; + } + dialog.acceptMode = true; + dialog.settings = settings; + GadgetCheckBoxSetChecked(dialog.windows, settings.windowsLogin); + dialog.decided = std::move(decided); + setState(State::Testing, "Connection test", TheGameText->fetch("WOL:Connecting")); + showDialog(); + startProbe(settings); +} +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/ServiceConnectionPolicy.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/ServiceConnectionPolicy.cpp new file mode 100644 index 00000000000..3a5a16c51e3 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/ServiceConnectionPolicy.cpp @@ -0,0 +1,1198 @@ +#include "GameNetwork/GeneralsOnline/ServiceConnectionPolicy.h" +#include "GameNetwork/GeneralsOnline/CredentialStore.h" + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "winhttp.lib") + +namespace ServiceConnection +{ +namespace +{ +// WinHTTP's proxy schemes; the precompiled header includes wininet.h, whose INTERNET_SCHEME_* values differ +constexpr INTERNET_SCHEME WinHttpSchemeHttp = (INTERNET_SCHEME)1; +constexpr INTERNET_SCHEME WinHttpSchemeHttps = (INTERNET_SCHEME)2; + +std::string Lower(std::string value) +{ + std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { return (char)std::tolower(c); }); + return value; +} + +std::wstring Wide(const std::string& value) +{ + if (value.empty()) + return {}; + int size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), (int)value.size(), nullptr, 0); + if (!size) + return {}; + std::wstring result(size, L'\0'); + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), (int)value.size(), &result[0], size); + return result; +} + +std::string UTF8(const std::wstring& value) +{ + if (value.empty()) + return {}; + int size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value.data(), (int)value.size(), nullptr, 0, nullptr, nullptr); + if (!size) + return {}; + std::string result(size, '\0'); + WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value.data(), (int)value.size(), &result[0], size, nullptr, nullptr); + return result; +} + +std::string Part(CURLU* url, CURLUPart part, unsigned flags = 0) +{ + char* value = nullptr; + if (curl_url_get(url, part, &value, flags) != CURLUE_OK) + return {}; + std::string result(value); + curl_free(value); + return result; +} + +bool CanonicalProxy(const std::string& typed, std::string& canonical, std::string& error) +{ + error.clear(); + // host:port without a scheme means an http proxy, so it maps to the same key as http://host:port + const std::string input = typed.find("://") == std::string::npos && !typed.empty() ? "http://" + typed : typed; + CURLU* url = curl_url(); + if (!url) + { + error = "Unable to allocate proxy URL parser."; + return false; + } + bool valid = input.find_first_of("\r\n\t ") == std::string::npos && + curl_url_set(url, CURLUPART_URL, input.c_str(), CURLU_NON_SUPPORT_SCHEME) == CURLUE_OK; + const auto scheme = Lower(Part(url, CURLUPART_SCHEME)); + const auto host = Lower(Part(url, CURLUPART_HOST)); + auto port = Part(url, CURLUPART_PORT); + const auto path = Part(url, CURLUPART_PATH); + const auto zone = Part(url, CURLUPART_ZONEID); + valid = valid && (scheme == "http" || scheme == "https" || scheme == "socks5h") && !host.empty() && zone.empty() && port != "0" && + input.find('@') == std::string::npos && Part(url, CURLUPART_USER).empty() && Part(url, CURLUPART_PASSWORD).empty() && + input.find('?') == std::string::npos && input.find('#') == std::string::npos && (path.empty() || path == "/"); + if (valid) + { + if (port.empty()) + port = scheme == "https" ? "443" : scheme == "http" ? "80" : "1080"; + canonical = scheme + "://" + host + ":" + port; + } + else + error = "Enter an http://, https:// or socks5h:// proxy address without credentials, path or query."; + curl_url_cleanup(url); + return valid; +} + + +struct SessionLogin +{ + std::string user; + std::string secret; +}; + +std::mutex& SessionLoginMutex() +{ + static std::mutex mutex; + return mutex; +} + +std::map& SessionLogins() +{ + static std::map logins; + return logins; +} + +void WipeSessionLogin(SessionLogin& login) +{ + if (!login.secret.empty()) + SecureZeroMemory(&login.secret[0], login.secret.size()); + login.secret.clear(); +} + +bool FindSessionLogin(const std::string& canonical, SessionLogin& login) +{ + std::lock_guard lock(SessionLoginMutex()); + const auto found = SessionLogins().find(canonical); + if (found == SessionLogins().end()) + return false; + login = found->second; + return true; +} + +bool Wildcard(const std::string& pattern, const std::string& value) +{ + size_t p = 0, v = 0, star = std::string::npos, retry = 0; + while (v < value.size()) + { + if (p < pattern.size() && (pattern[p] == '?' || pattern[p] == value[v])) + { + ++p; + ++v; + } + else if (p < pattern.size() && pattern[p] == '*') + { + star = p++; + retry = v; + } + else if (star != std::string::npos) + { + p = star + 1; + v = ++retry; + } + else + return false; + } + while (p < pattern.size() && pattern[p] == '*') + ++p; + return p == pattern.size(); +} + +std::vector Tokens(std::string input) +{ + std::replace(input.begin(), input.end(), ';', ' '); + std::istringstream stream(input); + std::vector result; + for (std::string token; stream >> token;) + result.push_back(token); + return result; +} + +bool StaticProxy(const std::wstring& proxy, const std::wstring& bypass, const std::string& destination, + ResolvedProxyPolicy& result) +{ + CURLU* url = curl_url(); + if (!url || curl_url_set(url, CURLUPART_URL, destination.c_str(), 0) != CURLUE_OK) + { + if (url) + curl_url_cleanup(url); + result.error = "Invalid proxy lookup destination."; + return false; + } + const auto host = Lower(Part(url, CURLUPART_HOST)); + const auto scheme = Lower(Part(url, CURLUPART_SCHEME)); + const auto port = Part(url, CURLUPART_PORT, CURLU_DEFAULT_PORT); + curl_url_cleanup(url); + for (auto token : Tokens(Lower(UTF8(bypass)))) + { + if ((token == "" && host.find('.') == std::string::npos && host.find(':') == std::string::npos) || + Wildcard(token, host) || Wildcard(token, host + ":" + port)) + { + result.routes.push_back({}); + return true; + } + } + bool specific = false; + bool socksFallback = false; + const auto entries = Tokens(UTF8(proxy)); + if (entries.empty()) + { + result.error = "Windows has an empty static proxy list."; + return false; + } + for (const auto& entry : entries) + { + const auto equal = entry.find('='); + if (equal == std::string::npos) + continue; + const auto protocol = Lower(entry.substr(0, equal)); + if (protocol == scheme) + specific = true; + if (protocol == "socks") + socksFallback = true; + } + for (auto entry : entries) + { + const auto equal = entry.find('='); + if (equal != std::string::npos) + { + if (Lower(entry.substr(0, equal)) != scheme) + continue; + entry.erase(0, equal + 1); + } + else if (specific) + continue; + if (entry.find("://") == std::string::npos) + entry = "http://" + entry; + std::string canonical; + if (!CanonicalProxy(entry, canonical, result.error)) + { + result.routes.clear(); + return false; + } + result.routes.push_back({canonical, false, canonical.find("socks5h://") != 0}); + } + // A protocol-specific system configuration without this protocol explicitly leaves it direct. + if (result.routes.empty()) + { + if (socksFallback) + { + result.error = "Windows SOCKS fallback is unsupported. Select an explicit supported proxy."; + return false; + } + result.routes.push_back({}); + } + return true; +} + +struct ResolverContext +{ + std::atomic references{1}; + std::atomic error{ERROR_IO_PENDING}; + HANDLE completed = CreateEventW(nullptr, TRUE, FALSE, nullptr); + HANDLE closing = CreateEventW(nullptr, TRUE, FALSE, nullptr); + void Release() + { + if (references.fetch_sub(1) == 1) + { + if (completed) + CloseHandle(completed); + if (closing) + CloseHandle(closing); + delete this; + } + } +}; + +void CALLBACK ProxyCallback(HINTERNET, DWORD_PTR value, DWORD status, void* information, DWORD) +{ + auto* context = reinterpret_cast(value); + if (!context) + return; + if (status == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) + { + // signal before releasing: this reference keeps the event alive + if (context->closing) + SetEvent(context->closing); + context->Release(); + return; + } + if (status == WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE || status == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) + { + DWORD error = status == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR ? + static_cast(information)->dwError : ERROR_SUCCESS; + context->error.store(error, std::memory_order_release); + SetEvent(context->completed); + } +} + +// websocket lookups use the matching http scheme +std::string ProxyDestination(std::string destination) +{ + if (destination.compare(0, 6, "wss://") == 0) + destination.replace(0, 6, "https://"); + else if (destination.compare(0, 5, "ws://") == 0) + destination.replace(0, 5, "http://"); + return destination; +} + +ResolvedProxyPolicy AutoProxy(const std::wstring& destination, WINHTTP_AUTOPROXY_OPTIONS& options) +{ + ResolvedProxyPolicy result; + HINTERNET session = WinHttpOpen(L"GeneralsOnline", WINHTTP_ACCESS_TYPE_NO_PROXY, nullptr, nullptr, WINHTTP_FLAG_ASYNC); + if (!session) + { + result.nativeError = GetLastError(); + result.failurePhase = "proxy-discovery"; + result.error = "Unable to initialize Windows proxy resolution."; + return result; + } + WinHttpSetTimeouts(session, 5000, 5000, 5000, 5000); + HINTERNET resolver = nullptr; + DWORD status = ERROR_SUCCESS; + if (WinHttpSetStatusCallback(session, ProxyCallback, WINHTTP_CALLBACK_FLAG_REQUEST_ERROR | + WINHTTP_CALLBACK_FLAG_GETPROXYFORURL_COMPLETE | WINHTTP_CALLBACK_FLAG_HANDLES, 0) == WINHTTP_INVALID_STATUS_CALLBACK) + status = GetLastError(); + if (!status) + status = WinHttpCreateProxyResolver(session, &resolver); + auto* context = new ResolverContext; + bool callbackOwnsReference = false; + if (!context->completed || !context->closing) + status = GetLastError(); + if (!status) + { + DWORD_PTR value = reinterpret_cast(context); + if (!WinHttpSetOption(resolver, WINHTTP_OPTION_CONTEXT_VALUE, &value, sizeof(value))) + status = GetLastError(); + else + { + ++context->references; + callbackOwnsReference = true; + } + } + if (!status) + { + status = WinHttpGetProxyForUrlEx(resolver, destination.c_str(), &options, reinterpret_cast(context)); + if (status == ERROR_IO_PENDING) + { + const DWORD wait = WaitForSingleObject(context->completed, 5000); + status = wait == WAIT_OBJECT_0 ? context->error.load(std::memory_order_acquire) : ERROR_TIMEOUT; + } + } + if (!status) + { + WINHTTP_PROXY_RESULT proxies{}; + status = WinHttpGetProxyResult(resolver, &proxies); + if (!status) + { + for (DWORD i = 0; i < proxies.cEntries; ++i) + { + const auto& entry = proxies.pEntries[i]; + if (!entry.fProxy) + { + result.routes.push_back({}); + continue; + } + std::string scheme; + if (entry.ProxyScheme == WinHttpSchemeHttp) + scheme = "http"; + else if (entry.ProxyScheme == WinHttpSchemeHttps) + scheme = "https"; + else + { + result.error = "Windows returned an unsupported proxy protocol."; + break; + } + std::string host = entry.pwszProxy ? UTF8(entry.pwszProxy) : std::string(); + if (host.find(':') != std::string::npos && host.front() != '[') + host = "[" + host + "]"; + std::string canonical; + if (!CanonicalProxy(scheme + "://" + host + ":" + std::to_string(entry.ProxyPort), canonical, result.error)) + break; + result.routes.push_back({canonical, false, canonical.find("socks5h://") != 0}); + } + WinHttpFreeProxyResult(&proxies); + } + } + if (resolver) + WinHttpCloseHandle(resolver); + // the resolver's final callback must run while its session is still open + if (callbackOwnsReference) + WaitForSingleObject(context->closing, 5000); + WinHttpCloseHandle(session); + // the callback owns the second reference, so a late or missed callback never frees a live context + context->Release(); + result.nativeError = status; + if (status) + result.error = "Windows proxy resolution failed (" + std::to_string(status) + "). Check system proxy settings."; + if (result.routes.empty() && result.error.empty()) + result.error = "Windows returned no permitted proxy route."; + if (!result.error.empty()) + { + result.routes.clear(); + result.failurePhase = status == ERROR_TIMEOUT || status == ERROR_WINHTTP_TIMEOUT ? "proxy-timeout" : + (options.dwFlags & WINHTTP_AUTOPROXY_CONFIG_URL) ? "proxy-pac" : "proxy-discovery"; + } + return result; +} + +// the first route for display +std::string SourceOfRoutes(const ResolvedProxyPolicy& result) +{ + return result.routes.empty() || result.routes.front().direct ? std::string("Direct") : ProxyDisplayName(result.routes.front().url); +} + +ResolvedProxyPolicy ApplyOptionalWpadFallback(ResolvedProxyPolicy result, bool autoDetect, bool explicitPac, + const std::wstring& proxy, const std::wstring& bypass, const std::string& destination) +{ + // No discovered WPAD script is different from failing to load or execute a configured script. + if (!autoDetect || explicitPac || result.nativeError != ERROR_WINHTTP_AUTODETECTION_FAILED || result.error.empty()) + return result; + result.routes.clear(); + result.error.clear(); + result.failurePhase.clear(); + if (!proxy.empty()) + { + if (!StaticProxy(proxy, bypass, destination, result)) + { + result.failurePhase = "proxy-configuration"; + return result; + } + result.fallbackReason = "No WPAD configuration found; using the system static proxy policy."; + } + else + { + result.routes.push_back({}); + result.fallbackReason = "No WPAD configuration or static proxy found; using direct access."; + } + return result; +} +} + +bool ValidateProxyURL(const std::string& proxyURL, std::string& error) +{ + std::string canonical; + return CanonicalProxy(proxyURL, canonical, error); +} + +namespace +{ +ResolvedProxyPolicy ResolveProxyRoutes(const ServiceConnectionSettings& settings, const std::string& destinationURL) +{ + ResolvedProxyPolicy result; + if (settings.proxyMode == "disabled") + { + result.routes.push_back({}); + result.source = "Direct"; + return result; + } + if (settings.proxyMode == "custom") + { + std::string canonical; + if (CanonicalProxy(settings.proxyURL, canonical, result.error)) + result.routes.push_back({canonical, false, canonical.find("socks5h://") != 0}); + else + result.failurePhase = "proxy-configuration"; + return result; + } + if (settings.proxyMode != "automatic") + { + result.error = "Unknown proxy mode."; + result.failurePhase = "proxy-configuration"; + return result; + } + const std::string destination = ProxyDestination(destinationURL); + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG config{}; + if (!WinHttpGetIEProxyConfigForCurrentUser(&config)) + { + result.nativeError = GetLastError(); + result.failurePhase = "proxy-settings"; + result.error = "Unable to read Windows proxy settings (" + std::to_string(result.nativeError) + ")."; + return result; + } + const std::wstring proxy = config.lpszProxy ? config.lpszProxy : L""; + const std::wstring bypass = config.lpszProxyBypass ? config.lpszProxyBypass : L""; + const std::wstring pac = config.lpszAutoConfigUrl ? config.lpszAutoConfigUrl : L""; + const bool autoDetect = config.fAutoDetect != FALSE; + GlobalFree(config.lpszProxy); GlobalFree(config.lpszProxyBypass); GlobalFree(config.lpszAutoConfigUrl); + if (!pac.empty() || autoDetect) + { + WINHTTP_AUTOPROXY_OPTIONS options{}; + if (!pac.empty()) + { + options.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL; + options.lpszAutoConfigUrl = pac.c_str(); + } + if (autoDetect) + { + options.dwFlags |= WINHTTP_AUTOPROXY_AUTO_DETECT; + options.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; + } + options.fAutoLogonIfChallenged = FALSE; + result = ApplyOptionalWpadFallback(AutoProxy(Wide(destination), options), autoDetect, !pac.empty(), + proxy, bypass, destination); + result.source = !result.fallbackReason.empty() ? SourceOfRoutes(result) : !pac.empty() ? "PAC: " + UTF8(pac) : "Auto-detect"; + return result; + } + if (!proxy.empty()) + { + if (!StaticProxy(proxy, bypass, destination, result)) + result.failurePhase = "proxy-configuration"; + result.source = SourceOfRoutes(result); + } + else + { + result.routes.push_back({}); + result.source = "Direct"; + } + return result; +} +} + +ResolvedProxyPolicy ResolveProxyPolicy(const ServiceConnectionSettings& settings, const std::string& destinationURL) +{ + ResolvedProxyPolicy result = ResolveProxyRoutes(settings, destinationURL); + for (auto& route : result.routes) + { + route.integratedAuthPossible = route.allowIntegratedAuth; + // the Windows login is only tried when the player wants it + if (!settings.windowsLogin) + route.allowIntegratedAuth = false; + } + return result; +} + +namespace +{ +using ProxyClock = std::chrono::steady_clock; +constexpr size_t MaxProxyJobs = 64; +constexpr size_t MaxProxyWaiters = 64; +constexpr size_t MaxProxyCache = 64; +constexpr auto ProxyCacheTTL = std::chrono::seconds(60); +constexpr auto ProxyFailureTTL = std::chrono::seconds(30); + +void KeyPart(std::string& key, const std::string& value) +{ + key += std::to_string(value.size()) + ":" + value; +} + +// Failures are shared per PAC server, not per URL; empty when no script is fetched +std::string PolicySource(const std::string& pacURL, bool autoDetect) +{ + if (pacURL.empty()) + { + return autoDetect ? "wpad" : std::string(); + } + const auto schemeEnd = pacURL.find("://"); + const auto start = schemeEnd == std::string::npos ? 0 : schemeEnd + 3; + return "pac:" + Lower(pacURL.substr(0, pacURL.find_first_of("/?#", start))); +} + +bool IsCacheableProxyFailure(const ResolvedProxyPolicy& result) +{ + return !result.error.empty() && (result.failurePhase == "proxy-timeout" || + result.failurePhase == "proxy-pac" || result.failurePhase == "proxy-discovery"); +} + +// In-memory only: PAC decisions can depend on the full path and query +bool ProxyKey(const ServiceConnectionSettings& settings, const std::string& destination, + std::string& key, ResolvedProxyPolicy& failure, std::string* systemFingerprint = nullptr, + std::string* failureSource = nullptr) +{ + KeyPart(key, ProxyDestination(destination)); + KeyPart(key, settings.proxyMode); + KeyPart(key, settings.proxyURL); + key += settings.windowsLogin ? "1" : "0"; + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG config{}; + if (!WinHttpGetIEProxyConfigForCurrentUser(&config)) + { + failure.nativeError = GetLastError(); + failure.failurePhase = "proxy-settings"; + failure.error = "Unable to read Windows proxy settings (" + std::to_string(failure.nativeError) + ")."; + return false; + } + const auto systemStart = key.size(); + key += config.fAutoDetect ? "1" : "0"; + KeyPart(key, config.lpszProxy ? UTF8(config.lpszProxy) : ""); + KeyPart(key, config.lpszProxyBypass ? UTF8(config.lpszProxyBypass) : ""); + KeyPart(key, config.lpszAutoConfigUrl ? UTF8(config.lpszAutoConfigUrl) : ""); + if (systemFingerprint) + { + *systemFingerprint = key.substr(systemStart); + } + if (failureSource) + { + *failureSource = PolicySource(config.lpszAutoConfigUrl ? UTF8(config.lpszAutoConfigUrl) : "", config.fAutoDetect != FALSE); + } + GlobalFree(config.lpszProxy); + GlobalFree(config.lpszProxyBypass); + GlobalFree(config.lpszAutoConfigUrl); + return true; +} + +struct ProxyWork +{ + ServiceConnectionSettings settings; + std::string destination; + std::string key; + std::string failureKey; + unsigned long long generation = 0; + std::vector> waiters; +}; +struct ProxyCacheEntry +{ + ResolvedProxyPolicy result; + ProxyClock::time_point expires; +}; +struct ProxyQueue +{ + std::mutex mutex; + std::deque> pending; + std::map> active; + std::map cache; + std::map failures; + std::string lastSystemKey; + unsigned long long generation = 0; + unsigned workers = 0; +}; +std::shared_ptr ProxyQueueState() +{ + static auto queue = std::make_shared(); + return queue; +} +void CompleteProxy(ProxyResolution& state, const ResolvedProxyPolicy& result) +{ + state.result = result; + state.ready.store(true, std::memory_order_release); +} +void ProxyQueueFailure(ProxyResolution& state, const char* message) +{ + ResolvedProxyPolicy result; + result.error = message; + result.failurePhase = "proxy-policy"; + CompleteProxy(state, result); +} +void PruneProxyEntries(std::map& entries, ProxyClock::time_point now) +{ + for (auto it = entries.begin(); it != entries.end();) + { + if (it->second.expires <= now) + { + it = entries.erase(it); + } + else + { + ++it; + } + } +} +void PruneProxyCache(ProxyQueue& queue, ProxyClock::time_point now) +{ + PruneProxyEntries(queue.cache, now); + PruneProxyEntries(queue.failures, now); +} +void FinishProxyWork(ProxyQueue& queue, const std::shared_ptr& work, + const ResolvedProxyPolicy& result, ProxyClock::time_point now) +{ + if (work->generation == queue.generation && result.error.empty() && !result.routes.empty()) + { + PruneProxyCache(queue, now); + if (queue.cache.size() >= MaxProxyCache) + { + queue.cache.erase(queue.cache.begin()); + } + queue.cache[work->key] = {result, now + ProxyCacheTTL}; + } + else if (work->generation == queue.generation && !work->failureKey.empty() && IsCacheableProxyFailure(result)) + { + PruneProxyCache(queue, now); + if (queue.failures.size() >= MaxProxyCache) + { + queue.failures.erase(queue.failures.begin()); + } + queue.failures[work->failureKey] = {result, now + ProxyFailureTTL}; + } + for (auto& waiter : work->waiters) + { + if (auto state = waiter.lock()) + { + CompleteProxy(*state, result); + } + } + const auto active = queue.active.find(work->key); + if (active != queue.active.end() && active->second == work) + { + queue.active.erase(active); + } +} +std::shared_ptr QueueProxyWork(ProxyQueue& queue, const ServiceConnectionSettings& settings, + const std::string& destinationURL, const std::string& key, const std::shared_ptr& state, + const std::string& failureKey = std::string(), ProxyClock::time_point now = ProxyClock::now()) +{ + const auto cached = queue.cache.find(key); + if (cached != queue.cache.end()) + { + CompleteProxy(*state, cached->second.result); + return {}; + } + // a dead PAC server is probed once per TTL, not once per URL + const auto failed = failureKey.empty() ? queue.failures.end() : queue.failures.find(failureKey); + if (failed != queue.failures.end() && failed->second.expires > now) + { + CompleteProxy(*state, failed->second.result); + return {}; + } + const auto active = queue.active.find(key); + if (active != queue.active.end()) + { + auto& waiters = active->second->waiters; + waiters.erase(std::remove_if(waiters.begin(), waiters.end(), + [](const std::weak_ptr& waiter) { return waiter.expired(); }), waiters.end()); + if (waiters.size() >= MaxProxyWaiters) + { + ProxyQueueFailure(*state, "Too many pending proxy lookups. Try again shortly."); + } + else + { + waiters.push_back(state); + } + return {}; + } + if (queue.pending.size() + queue.workers >= MaxProxyJobs) + { + ProxyQueueFailure(*state, "Too many pending proxy lookups. Try again shortly."); + return {}; + } + auto work = std::make_shared(); + work->settings = settings; + work->destination = destinationURL; + work->key = key; + work->failureKey = failureKey; + work->generation = queue.generation; + work->waiters.push_back(state); + queue.active[work->key] = work; + queue.pending.push_back(work); + return work; +} + +} + +void InvalidateProxyPolicyCache() +{ + auto queue = ProxyQueueState(); + std::lock_guard lock(queue->mutex); + ++queue->generation; + queue->cache.clear(); + queue->failures.clear(); + // callers already waiting keep their lookup, new callers start a fresh one + queue->active.clear(); +} + +bool ShouldLogProxyPolicy(bool websocket, const ResolvedProxyPolicy& result) +{ + static std::mutex mutex; + static std::string previous[2]; + std::string signature; + KeyPart(signature, result.fallbackReason); + for (const auto& route : result.routes) + { + signature += route.direct ? "D" : "P"; + KeyPart(signature, route.url); + signature += route.allowIntegratedAuth ? "1" : "0"; + } + std::lock_guard lock(mutex); + auto& last = previous[websocket ? 1 : 0]; + if (last == signature) + { + return false; + } + last = std::move(signature); + return true; +} + +std::shared_ptr StartProxyResolution(ServiceConnectionSettings settings, std::string destinationURL) +{ + auto state = std::make_shared(); + if (destinationURL.size() > 16384 || settings.proxyURL.size() > 2048) + { + ProxyQueueFailure(*state, "Proxy lookup configuration is too long."); + return state; + } + // Explicit direct/custom policy needs neither discovery nor a background worker + if (settings.proxyMode != "automatic") + { + CompleteProxy(*state, ResolveProxyPolicy(settings, destinationURL)); + return state; + } + std::string key, systemFingerprint, failureSource; + ResolvedProxyPolicy failure; + if (!ProxyKey(settings, destinationURL, key, failure, &systemFingerprint, &failureSource)) + { + CompleteProxy(*state, failure); + return state; + } + auto queue = ProxyQueueState(); + std::lock_guard lock(queue->mutex); + if (queue->lastSystemKey != systemFingerprint) + { + ++queue->generation; + queue->cache.clear(); + queue->failures.clear(); + queue->active.clear(); + queue->lastSystemKey = std::move(systemFingerprint); + } + const auto now = ProxyClock::now(); + PruneProxyCache(*queue, now); + auto work = QueueProxyWork(*queue, settings, destinationURL, key, state, failureSource, now); + if (!work) + { + return state; + } + if (queue->workers >= 4) + { + return state; + } + ++queue->workers; + try + { + std::thread([queue]() { + for (;;) + { + std::shared_ptr work; + { + std::lock_guard lock(queue->mutex); + if (queue->pending.empty()) + { + --queue->workers; + return; + } + work = std::move(queue->pending.front()); + queue->pending.pop_front(); + const bool canceled = std::all_of(work->waiters.begin(), work->waiters.end(), + [](const std::weak_ptr& waiter) { return waiter.expired(); }); + if (canceled) + { + const auto active = queue->active.find(work->key); + if (active != queue->active.end() && active->second == work) + { + queue->active.erase(active); + } + continue; + } + } + ResolvedProxyPolicy result; + try + { + result = ResolveProxyPolicy(work->settings, work->destination); + std::string currentKey; + ResolvedProxyPolicy changed; + if (!ProxyKey(work->settings, work->destination, currentKey, changed)) + { + result = std::move(changed); + } + else if (currentKey != work->key) + { + result = {}; + result.error = "Windows proxy settings changed during lookup. Reconnect to retry."; + result.failurePhase = "proxy-settings"; + } + } + catch (...) + { + result.error = "Proxy resolution failed."; + result.failurePhase = "proxy-policy"; + } + std::lock_guard lock(queue->mutex); + FinishProxyWork(*queue, work, result, ProxyClock::now()); + } + }).detach(); + } + catch (...) + { + --queue->workers; + queue->pending.pop_back(); + queue->active.erase(work->key); + ProxyQueueFailure(*state, "Unable to start proxy resolution."); + } + return state; +} + +bool SaveProxyCredentials(const std::string& proxyURL, const std::string& username, + const std::string& password, std::string& error) +{ + std::string canonical; + if (!CanonicalProxy(proxyURL, canonical, error)) + return false; + if (username.empty() || username.find('\0') != std::string::npos || password.find('\0') != std::string::npos) + { + error = "Proxy credentials are invalid."; + return false; + } + if (!SaveProxyLogin(canonical, username, password)) + { + error = "Unable to save proxy credentials. They are used until the game closes."; + return false; + } + return true; +} + +void SetSessionProxyCredentials(const std::string& proxyURL, const std::string& username, const std::string& password) +{ + std::string canonical, error; + if (!CanonicalProxy(proxyURL, canonical, error)) + return; + std::lock_guard lock(SessionLoginMutex()); + auto& login = SessionLogins()[canonical]; + WipeSessionLogin(login); + login.user = username; + login.secret = password; +} + +void ClearSessionProxyCredentials(const std::string& proxyURL) +{ + std::string canonical, error; + if (!CanonicalProxy(proxyURL, canonical, error)) + return; + std::lock_guard lock(SessionLoginMutex()); + const auto found = SessionLogins().find(canonical); + if (found == SessionLogins().end()) + return; + WipeSessionLogin(found->second); + SessionLogins().erase(found); +} + +void ClearSessionProxyCredentialsExcept(const std::string& keepProxyURL) +{ + std::string keep, error; + CanonicalProxy(keepProxyURL, keep, error); + std::lock_guard lock(SessionLoginMutex()); + for (auto it = SessionLogins().begin(); it != SessionLogins().end();) + { + if (it->first == keep) + { + ++it; + continue; + } + WipeSessionLogin(it->second); + it = SessionLogins().erase(it); + } +} + +std::string CanonicalProxyKey(const std::string& proxyURL) +{ + std::string canonical, error; + return CanonicalProxy(proxyURL, canonical, error) ? canonical : std::string(); +} + +std::string ProxyUsername(const std::string& proxyURL) +{ + std::string canonical, error; + if (!CanonicalProxy(proxyURL, canonical, error)) + return {}; + SessionLogin session; + if (FindSessionLogin(canonical, session)) + { + WipeSessionLogin(session); + return session.user; + } + std::string user, password; + const bool found = LoadProxyLogin(canonical, user, password); + if (!password.empty()) + SecureZeroMemory(&password[0], password.size()); + return found ? user : std::string(); +} + +std::string ProxyDisplayName(const std::string& proxyURL) +{ + const auto schemeEnd = proxyURL.find("://"); + return schemeEnd == std::string::npos ? proxyURL : proxyURL.substr(schemeEnd + 3); +} + +bool HasProxyCredentials(const std::string& proxyURL) +{ + std::string canonical, error; + std::string user, password; + const bool found = CanonicalProxy(proxyURL, canonical, error) && LoadProxyLogin(canonical, user, password); + if (!password.empty()) + SecureZeroMemory(&password[0], password.size()); + return found; +} + +bool DeleteProxyCredentials(const std::string& proxyURL, std::string& error) +{ + std::string canonical; + if (!CanonicalProxy(proxyURL, canonical, error)) + return false; + if (DeleteProxyLogin(canonical)) + return true; + error = "Unable to remove proxy credentials."; + return false; +} + +bool ConfigureCurl(CURL* curl, const std::string& destinationURL, const ProxyRoute& route, std::string& error) +{ + if (!ConfigureServiceTransport(curl, destinationURL, error)) + return false; + const bool websocket = destinationURL.compare(0, 6, "wss://") == 0; + auto set = [&](CURLoption option, auto value) { + CURLcode code = curl_easy_setopt(curl, option, value); + if (code != CURLE_OK && error.empty()) + error = "Unable to configure service transport: " + std::string(curl_easy_strerror(code)); + return code == CURLE_OK; + }; + const auto* version = curl_version_info(CURLVERSION_NOW); + const long protocol = websocket ? CURL_HTTP_VERSION_1_1 : + route.direct && (version->features & CURL_VERSION_HTTP3) ? CURL_HTTP_VERSION_3 : + (version->features & CURL_VERSION_HTTP2) ? CURL_HTTP_VERSION_2TLS : CURL_HTTP_VERSION_1_1; + if (!set(CURLOPT_HTTP_VERSION, protocol) || !set(CURLOPT_PROXY_SSL_VERIFYPEER, 1L) || !set(CURLOPT_PROXY_SSL_VERIFYHOST, 2L) || + !set(CURLOPT_PROXY_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA) || !set(CURLOPT_SOCKS5_AUTH, (long)CURLAUTH_BASIC) || + !set(CURLOPT_HEADEROPT, (long)CURLHEADER_SEPARATE) || !set(CURLOPT_NOPROXY, "") || !set(CURLOPT_PROXYAUTH, (long)CURLAUTH_NONE)) + return false; + if (route.direct) + return true; + std::string canonical; + if (!CanonicalProxy(route.url, canonical, error)) + return false; + if (!set(CURLOPT_PROXY, canonical.c_str())) + return false; + if (route.allowIntegratedAuth) + { + if (!(version->features & CURL_VERSION_SSPI)) + { + error = "This build does not support Windows proxy authentication."; + return false; + } + return set(CURLOPT_PROXYAUTH, (long)CURLAUTH_NEGOTIATE) && set(CURLOPT_PROXYUSERPWD, ":"); + } + // otherwise a login typed for this session wins over the saved one + SessionLogin session; + if (FindSessionLogin(canonical, session)) + { + const bool success = set(CURLOPT_PROXYUSERNAME, session.user.c_str()) && set(CURLOPT_PROXYPASSWORD, session.secret.c_str()) && + set(CURLOPT_PROXYAUTH, (long)(CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_NEGOTIATE)); + WipeSessionLogin(session); + return success; + } + std::string user, secret; + if (LoadProxyLogin(canonical, user, secret)) + { + const bool success = set(CURLOPT_PROXYUSERNAME, user.c_str()) && set(CURLOPT_PROXYPASSWORD, secret.c_str()) && + set(CURLOPT_PROXYAUTH, (long)(CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_NEGOTIATE)); + if (!secret.empty()) + SecureZeroMemory(&secret[0], secret.size()); + return success; + } + return true; +} + +const char* ProtocolName(long version) +{ + switch (version) + { + case CURL_HTTP_VERSION_1_0: return "HTTP/1.0"; + case CURL_HTTP_VERSION_1_1: return "HTTP/1.1"; + case CURL_HTTP_VERSION_2_0: return "HTTP/2"; + case CURL_HTTP_VERSION_3: return "HTTP/3"; + default: return "unknown"; + } +} + +const char* FailurePhase(CURLcode code, long status, long proxyStatus) +{ + if (proxyStatus == 407 || status == 407) + return "proxy authentication"; + if (code == CURLE_COULDNT_RESOLVE_PROXY) + return "proxy DNS"; + if (code == CURLE_COULDNT_RESOLVE_HOST) + return "endpoint DNS"; + if (code == CURLE_PEER_FAILED_VERIFICATION || code == CURLE_SSL_CONNECT_ERROR || + code == CURLE_SSL_CACERT_BADFILE || code == CURLE_SSL_CERTPROBLEM) + return "TLS"; + if (code == CURLE_COULDNT_CONNECT) + return "connection"; + if (code == CURLE_OPERATION_TIMEDOUT) + return "timeout"; + if (status == 401 || status == 403) + return "service authentication"; + if (code != CURLE_OK) + return "transport"; + if (status >= 400) + return "service response"; + return "none"; +} + +namespace +{ +size_t ReceiveProbe(void* data, size_t size, size_t count, void* target) +{ + if (size && count > SIZE_MAX / size) + return 0; + const size_t bytes = size * count; + auto& body = *static_cast(target); + if (bytes > 4096 || body.size() > 4096 - bytes) + return 0; + body.append(static_cast(data), bytes); + return bytes; +} +} + +ConnectionTestResult TestServiceConnection(const ServiceConnectionSettings& settings, const std::string& environment, int maxSeconds) +{ + using Outcome = ConnectionTestResult::Outcome; + ConnectionTestResult result; + result.outcome = Outcome::Unreachable; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(maxSeconds); + for (const auto& base : BootstrapRoutes(environment, settings, "")) + { + const std::string url = base + "VersionManifest"; + const std::string routeName = RouteHost(base); + const auto policy = ResolveProxyPolicy(settings, url); + if (!policy.error.empty()) + { + result.outcome = Outcome::Failed; + result.detail = policy.error; + return result; + } + for (const auto& route : policy.routes) + { + if (std::chrono::steady_clock::now() >= deadline) + return result; + std::unique_ptr curl(curl_easy_init(), curl_easy_cleanup); + std::string error, body; + if (!ConfigureCurl(curl.get(), url, route, error)) + { + result.outcome = Outcome::Failed; + result.detail = error; + return result; + } + if (curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()) != CURLE_OK || + curl_easy_setopt(curl.get(), CURLOPT_CONNECTTIMEOUT_MS, 4000L) != CURLE_OK || + curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT_MS, 6000L) != CURLE_OK || + curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, ReceiveProbe) != CURLE_OK || + curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &body) != CURLE_OK) + { + result.outcome = Outcome::Failed; + result.detail = "Could not set up the test."; + return result; + } + const auto code = curl_easy_perform(curl.get()); + long status = 0, proxyStatus = 0; + curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &status); + curl_easy_getinfo(curl.get(), CURLINFO_HTTP_CONNECTCODE, &proxyStatus); + result.route = routeName; + result.proxyURL = route.direct ? std::string() : route.url; + result.proxyAnswered = proxyStatus != 0; + result.proxyIntegratedAuthPossible = !route.direct && route.integratedAuthPossible; + if (status == 407 || proxyStatus == 407) + { + result.outcome = Outcome::ProxyAuthRequired; + result.detail = ProxyUsername(route.url).empty() ? "required" : "refused"; + return result; + } + if (status == 401 || status == 403) + { + result.outcome = Outcome::ServiceRejected; + return result; + } + if (code == CURLE_OK && status == 200 && IsExpectedVersionManifest(body)) + { + result.outcome = Outcome::Connected; + return result; + } + if (code == CURLE_PEER_FAILED_VERIFICATION || code == CURLE_SSL_CONNECT_ERROR) + { + result.outcome = Outcome::Failed; + result.detail = "Secure connection failed. Check certificates or TLS inspection."; + } + else if (code == CURLE_OK) + { + result.outcome = Outcome::Failed; + result.detail = "Unexpected reply from the service (HTTP " + std::to_string(status) + ")."; + } + else + result.outcome = Outcome::Unreachable; + } + } + return result; +} + +std::string DescribeConnectionTest(const ConnectionTestResult& result) +{ + using Outcome = ConnectionTestResult::Outcome; + switch (result.outcome) + { + case Outcome::Connected: + return result.proxyURL.empty() ? "Connected through the " + result.route + " route, no proxy." : "Connected through proxy " + ProxyDisplayName(result.proxyURL) + "."; + case Outcome::ProxyAuthRequired: + return result.detail == "required" ? "The proxy needs a login." : "The proxy refused the login."; + case Outcome::ServiceRejected: + return "The service refused the connection."; + case Outcome::Unreachable: + return std::string("Could not reach the service.") + (result.proxyURL.empty() ? "\nCheck your internet connection." : "\nCheck the proxy address."); + default: + return result.detail.empty() ? "Could not test the connection." : result.detail; + } +} +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/ServiceEndpointPolicy.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/ServiceEndpointPolicy.cpp new file mode 100644 index 00000000000..bae59e13971 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/ServiceEndpointPolicy.cpp @@ -0,0 +1,275 @@ +#include "GameNetwork/GeneralsOnline/ServiceEndpointPolicy.h" +#include "GameNetwork/GeneralsOnline/json.hpp" +#include +#include +#include + +namespace ServiceConnection +{ +static std::string Lower(std::string value); +static std::string Part(CURLU* url, CURLUPart part); + +bool NormalizeCustomEndpoint(const std::string& typed, std::string& origin, std::string& error) +{ + const std::string input = typed; + origin.clear(); + error.clear(); + size_t first = input.find_first_not_of(" \t"); + if (first == std::string::npos) + { + error = "Enter the server address."; + return false; + } + std::string value = input.substr(first, input.find_last_not_of(" \t") - first + 1); + if (std::any_of(value.begin(), value.end(), [](unsigned char c) { return c <= 32 || c >= 127 || c == '\\'; })) + { + error = "The server address has characters that are not allowed."; + return false; + } + const std::string lower = Lower(value); + if (lower.compare(0, 7, "http://") == 0) + { + error = "The server must use https. Plain http is not allowed."; + return false; + } + if (lower.compare(0, 8, "https://") == 0) + value.erase(0, 8); + else if (value.find("://") != std::string::npos) + { + error = "The server must use https."; + return false; + } + if (value.find('@') != std::string::npos) + { + error = "The server address can't contain a user name or password."; + return false; + } + if (value.find('?') != std::string::npos) + { + error = "The server address can't contain a query."; + return false; + } + if (value.find('#') != std::string::npos) + { + error = "The server address can't contain a fragment."; + return false; + } + const auto slash = value.find('/'); + if (slash != std::string::npos && slash + 1 != value.size()) + { + error = "Enter only the host and port, without a path."; + return false; + } + if (slash != std::string::npos) + value.erase(slash); + std::unique_ptr url(curl_url(), curl_url_cleanup); + if (!url || curl_url_set(url.get(), CURLUPART_URL, ("https://" + value).c_str(), 0) != CURLUE_OK) + { + error = "Enter a host name with an optional port, like server.example:8443."; + return false; + } + const auto host = Lower(Part(url.get(), CURLUPART_HOST)); + const auto port = Part(url.get(), CURLUPART_PORT); + if (host.empty() || host.find('%') != std::string::npos || !Part(url.get(), CURLUPART_ZONEID).empty() || port == "0") + { + error = "Enter a host name with an optional port, like server.example:8443."; + return false; + } + origin = "https://" + host + (port.empty() || port == "443" ? "" : ":" + port); + return true; +} + +std::string CredentialServerKey(const ServiceConnectionSettings& settings) +{ + if (settings.serverMode != "custom") + return "go"; + std::string origin, error; + return NormalizeCustomEndpoint(settings.serverAddress, origin, error) ? "custom:" + origin : std::string(); +} + +std::vector BootstrapRoutes(const std::string& environment, + const ServiceConnectionSettings& settings, const std::string& remembered) +{ + // a custom server is the only route there is: no other server is tried for it + if (settings.serverMode == "custom") + { + std::string origin, error; + if ((environment != "dev" && environment != "test" && environment != "prod") || !NormalizeCustomEndpoint(settings.serverAddress, origin, error)) + return {}; + return {origin + "/env/" + environment + "/contract/1/"}; + } + if (environment == "dev") + return {"https://localhost:9000/env/dev/contract/1/"}; + if (environment == "test") + return {"https://api.playgenerals.online:2087/env/test/contract/1/"}; + if (environment != "prod") + return {}; + if (settings.serverMode != "automatic") + return {}; + std::vector routes = { + "https://api.playgenerals.online/env/prod/contract/1/", + "https://api-ru.playgenerals.online/env/prod/contract/1/", + }; + const auto remembered_route = std::find_if(routes.begin(), routes.end(), [&](const std::string& route) { return RouteHost(route) == remembered; }); + if (remembered_route != routes.end()) + std::rotate(routes.begin(), remembered_route, remembered_route + 1); + return routes; +} + +std::string RouteHost(const std::string& route) +{ + const auto schemeEnd = route.find("://"); + if (schemeEnd == std::string::npos) + return {}; + const auto start = schemeEnd + 3; + return route.substr(start, route.find_first_of(":/", start) - start); +} + +static std::string Lower(std::string value) +{ + std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { return static_cast(tolower(c)); }); + return value; +} + +static bool EndsWith(const std::string& value, const std::string& suffix) +{ + return value.size() >= suffix.size() && value.compare(value.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +static std::string Part(CURLU* url, CURLUPart part) +{ + char* value = nullptr; + if (curl_url_get(url, part, &value, CURLU_DEFAULT_PORT) != CURLUE_OK) + return {}; + std::string result(value); + curl_free(value); + return result; +} + +bool IsTrustedServiceURL(const std::string& value, const std::string& base, bool websocket) +{ + if (value.find_first_of("\\\r\n\t #") != std::string::npos) + return false; + const auto schemeEnd = value.find("://"); + if (schemeEnd == std::string::npos) + return false; + const auto authorityEnd = value.find_first_of("/?", schemeEnd + 3); + const auto authority = value.substr(schemeEnd + 3, + authorityEnd == std::string::npos ? std::string::npos : authorityEnd - schemeEnd - 3); + if (authority.find('@') != std::string::npos || (websocket && value.find('?') != std::string::npos)) + return false; + std::string rawPath; + if (authorityEnd != std::string::npos && value[authorityEnd] == '/') + rawPath = value.substr(authorityEnd, value.find('?', authorityEnd) - authorityEnd); + // query escaping is legitimate; path separators and dot segments must not change after validation + const auto lowerPath = Lower(rawPath); + if ((websocket && rawPath.find('%') != std::string::npos) || lowerPath.find("%2e") != std::string::npos || + lowerPath.find("%2f") != std::string::npos || lowerPath.find("%5c") != std::string::npos || + rawPath.find("/../") != std::string::npos || rawPath.find("/./") != std::string::npos || + EndsWith(rawPath, "/..") || EndsWith(rawPath, "/.")) + return false; + std::unique_ptr url(curl_url(), curl_url_cleanup); + std::unique_ptr expected(curl_url(), curl_url_cleanup); + if (!url || !expected || curl_url_set(url.get(), CURLUPART_URL, value.c_str(), CURLU_NON_SUPPORT_SCHEME) != CURLUE_OK || + curl_url_set(expected.get(), CURLUPART_URL, base.c_str(), 0) != CURLUE_OK) + return false; + if (Part(url.get(), CURLUPART_SCHEME) != (websocket ? "wss" : "https")) + return false; + auto port = Part(url.get(), CURLUPART_PORT); + if (port.empty() && websocket) + port = "443"; + if (Lower(Part(url.get(), CURLUPART_HOST)) != Lower(Part(expected.get(), CURLUPART_HOST)) || + port != Part(expected.get(), CURLUPART_PORT)) + return false; + const auto path = Part(url.get(), CURLUPART_PATH); + const auto expectedPath = Part(expected.get(), CURLUPART_PATH); + if (!websocket) + return path.compare(0, expectedPath.size(), expectedPath) == 0; + return EndsWith(path, expectedPath + "ws"); +} + +std::string WebSocketURLForBase(const std::string& base) +{ + if (base.compare(0, 8, "https://") != 0) + return {}; + return "wss://" + base.substr(8) + "ws"; +} + +std::string RequestURLForLog(const std::string& value, const std::string& serviceBase) +{ + if (value.size() > 8192 || std::any_of(value.begin(), value.end(), [](unsigned char c) { return c <= 32 || c >= 127; })) + return "[invalid-url]"; + const auto schemeEnd = value.find("://"); + if (schemeEnd == std::string::npos) + return "[invalid-url]"; + const auto authority = value.substr(schemeEnd + 3, value.find_first_of("/?#", schemeEnd + 3) - schemeEnd - 3); + if (authority.find('@') != std::string::npos) + return "[redacted-url]"; + std::unique_ptr url(curl_url(), curl_url_cleanup); + if (!url || curl_url_set(url.get(), CURLUPART_URL, value.c_str(), 0) != CURLUE_OK || Part(url.get(), CURLUPART_SCHEME) != "https") + return "[invalid-url]"; + const auto host = Lower(Part(url.get(), CURLUPART_HOST)); + if (host.empty() || host.find('%') != std::string::npos || !Part(url.get(), CURLUPART_ZONEID).empty()) + return "[invalid-url]"; + const auto port = Part(url.get(), CURLUPART_PORT); + const std::string origin = "https://" + host + (port.empty() || port == "443" ? "" : ":" + port); + const auto path = Part(url.get(), CURLUPART_PATH); + // only known service routes are logged; other paths may carry private identifiers + std::unique_ptr base(curl_url(), curl_url_cleanup); + if (path.find('%') != std::string::npos || !IsTrustedServiceURL(origin + path, serviceBase, false) || + !base || curl_url_set(base.get(), CURLUPART_URL, serviceBase.c_str(), 0) != CURLUE_OK) + return origin + "/[redacted]"; + const auto prefix = Part(base.get(), CURLUPART_PATH); + const auto action = path.substr(prefix.size()); + static const char* const routes[] = { + "Matchmaking/Playlists", "Matchmaking/Widen", "PlayerStats/Batch", + "Social/Friends/Requests", "Social/Friends", "Social/Blocked", + "VersionManifest", "VersionCheck", "CheckLogin", "LoginWithToken", "LoginCode", "RefreshToken", + "ProvideMWToken", "ConnectionOutcome", "GlobalStats", "ServiceConfig", "Matchmaking", + "PlayerStats", "Lobbies", "Lobby", "Rooms", "MOTD", "User" + }; + for (const std::string name : routes) + { + if (action == name || action.compare(0, name.size() + 1, name + "/") == 0) + return origin + prefix + name + (action.size() == name.size() ? "" : "/[redacted]"); + } + return origin + prefix + "[redacted]"; +} + +bool IsExpectedVersionManifest(const std::string& body) +{ + const auto json = nlohmann::json::parse(body, nullptr, false); + return json.is_object() && json.contains("execrc_30") && json.contains("execrc_60") && + json["execrc_30"].is_number_unsigned() && json["execrc_60"].is_number_unsigned() && + json["execrc_30"].get() <= UINT32_MAX && json["execrc_60"].get() <= UINT32_MAX; +} + +bool ConfigureServiceTransport(CURL* curl, const std::string& url, std::string& error) +{ + error.clear(); + const bool websocket = url.compare(0, 6, "wss://") == 0; + if (!curl || (!websocket && url.compare(0, 8, "https://") != 0)) + { + error = "Service connections require HTTPS or WSS."; + return false; + } + auto set = [&](CURLoption option, auto value) { + const CURLcode code = curl_easy_setopt(curl, option, value); + if (code != CURLE_OK && error.empty()) + error = "Unable to configure service transport: " + std::string(curl_easy_strerror(code)); + return code == CURLE_OK; + }; +#if _DEBUG + // debug builds skip certificate checks for local service testing + constexpr long verifyPeer = 0L; + constexpr long verifyHost = 0L; +#else + constexpr long verifyPeer = 1L; + constexpr long verifyHost = 2L; +#endif + return set(CURLOPT_SSL_VERIFYPEER, verifyPeer) && set(CURLOPT_SSL_VERIFYHOST, verifyHost) && + set(CURLOPT_SSL_OPTIONS, static_cast(CURLSSLOPT_NATIVE_CA)) && + set(CURLOPT_DISALLOW_USERNAME_IN_URL, 1L) && set(CURLOPT_FOLLOWLOCATION, 0L) && + set(CURLOPT_PROTOCOLS_STR, websocket ? "wss" : "https") && set(CURLOPT_PROXY, ""); +} +} diff --git a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt index 56408e821bd..d251e04bb38 100644 --- a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt @@ -81,8 +81,6 @@ set(GAMEENGINEDEVICE_SRC Include/W3DDevice/GameLogic/W3DGameLogic.h Include/W3DDevice/GameLogic/W3DGhostObject.h Include/W3DDevice/GameLogic/W3DTerrainLogic.h -# Include/Win32Device/Common/Win32BIGFile.h -# Include/Win32Device/Common/Win32BIGFileSystem.h Include/Win32Device/Common/Win32GameEngine.h # Include/Win32Device/Common/Win32LocalFile.h # Include/Win32Device/Common/Win32LocalFileSystem.h @@ -181,8 +179,6 @@ set(GAMEENGINEDEVICE_SRC Source/W3DDevice/GameLogic/W3DGameLogic.cpp Source/W3DDevice/GameLogic/W3DGhostObject.cpp Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp -# Source/Win32Device/Common/Win32BIGFile.cpp -# Source/Win32Device/Common/Win32BIGFileSystem.cpp Source/Win32Device/Common/Win32GameEngine.cpp # Source/Win32Device/Common/Win32LocalFile.cpp # Source/Win32Device/Common/Win32LocalFileSystem.cpp diff --git a/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h b/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h index 2710cdf4555..e27eb8504c9 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h @@ -32,12 +32,12 @@ #pragma once +#include "Common/BIGFileSystem.h" #include "Common/GameEngine.h" #include "GameClient/ParticleSys.h" #include "GameLogic/GameLogic.h" #include "GameNetwork/NetworkInterface.h" #include "MilesAudioDevice/MilesAudioManager.h" -#include "Win32Device/Common/Win32BIGFileSystem.h" #include "Win32Device/Common/Win32LocalFileSystem.h" #include "W3DDevice/Common/W3DModuleFactory.h" #include "W3DDevice/GameLogic/W3DGameLogic.h" @@ -93,7 +93,7 @@ inline ModuleFactory *Win32GameEngine::createModuleFactory() { return NEW W3DMod inline ThingFactory *Win32GameEngine::createThingFactory() { return NEW W3DThingFactory; } inline FunctionLexicon *Win32GameEngine::createFunctionLexicon() { return NEW W3DFunctionLexicon; } inline LocalFileSystem *Win32GameEngine::createLocalFileSystem() { return NEW Win32LocalFileSystem; } -inline ArchiveFileSystem *Win32GameEngine::createArchiveFileSystem() { return NEW Win32BIGFileSystem; } +inline ArchiveFileSystem *Win32GameEngine::createArchiveFileSystem() { return NEW BIGFileSystem; } inline ParticleSystemManager* Win32GameEngine::createParticleSystemManager(Bool dummy) { if (dummy) diff --git a/GeneralsMD/Code/Tools/GUIEdit/Source/GUIEdit.cpp b/GeneralsMD/Code/Tools/GUIEdit/Source/GUIEdit.cpp index 2cbc8ec3ffb..6ecfad34952 100644 --- a/GeneralsMD/Code/Tools/GUIEdit/Source/GUIEdit.cpp +++ b/GeneralsMD/Code/Tools/GUIEdit/Source/GUIEdit.cpp @@ -85,7 +85,7 @@ #include "Win32Device/GameClient/Win32Mouse.h" #include "Win32Device/GameClient/Win32DIKeyboard.h" #include "Win32Device/Common/Win32LocalFileSystem.h" -#include "Win32Device/Common/Win32BIGFileSystem.h" +#include "Common/BIGFileSystem.h" #include "resource.h" #include "WinMain.h" @@ -485,7 +485,7 @@ void GUIEdit::init() // create file system TheFileSystem = new FileSystem; TheLocalFileSystem = new Win32LocalFileSystem; - TheArchiveFileSystem = new Win32BIGFileSystem; + TheArchiveFileSystem = new BIGFileSystem; TheFileSystem->init(); //--------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp index d49589ee0c8..65d2fb82b8f 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp @@ -79,7 +79,7 @@ #include #include "Win32Device/GameClient/Win32Mouse.h" #include "Win32Device/Common/Win32LocalFileSystem.h" -#include "Win32Device/Common/Win32BIGFileSystem.h" +#include "Common/BIGFileSystem.h" static SubsystemInterfaceList TheSubsystemListRecord; @@ -326,7 +326,7 @@ BOOL CWorldBuilderApp::InitInstance() TheFileSystem = new FileSystem; initSubsystem(TheLocalFileSystem, (LocalFileSystem*)new Win32LocalFileSystem); - initSubsystem(TheArchiveFileSystem, (ArchiveFileSystem*)new Win32BIGFileSystem); + initSubsystem(TheArchiveFileSystem, (ArchiveFileSystem*)new BIGFileSystem); // Just for kicks, get the HINSTANCE that WOL would need // if we were going to use it, which we aren't. diff --git a/GeneralsOnline/GameData/Window/Menus/MainMenuVersion.wnd b/GeneralsOnline/GameData/Window/Menus/MainMenuVersion.wnd new file mode 100644 index 00000000000..95c759af8b1 --- /dev/null +++ b/GeneralsOnline/GameData/Window/Menus/MainMenuVersion.wnd @@ -0,0 +1,54 @@ +FILE_VERSION = 2; +STARTLAYOUTBLOCK + LAYOUTINIT = [None]; + LAYOUTUPDATE = [None]; + LAYOUTSHUTDOWN = [None]; +ENDLAYOUTBLOCK +WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 12 580, + BOTTOMRIGHT: 511 592, + CREATIONRESOLUTION: 800 600; + NAME = "MainMenuVersion.wnd:LabelVersion"; + STATUS = ENABLED+NOINPUT; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 8, BOLD: 0; + HEADERTEMPLATE = "LabelSmall"; + TOOLTIPDELAY = -1; + TEXT = "Version"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 46 81 0 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 1 1 0 0, BORDERCOLOR: 1 1 1 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; +END diff --git a/GeneralsOnline/GameData/Window/Menus/OptionsMenu.wnd b/GeneralsOnline/GameData/Window/Menus/OptionsMenu.wnd new file mode 100644 index 00000000000..8290c516f26 --- /dev/null +++ b/GeneralsOnline/GameData/Window/Menus/OptionsMenu.wnd @@ -0,0 +1,5923 @@ +FILE_VERSION = 2; +STARTLAYOUTBLOCK + LAYOUTINIT = OptionsMenuInit; + LAYOUTUPDATE = OptionsMenuUpdate; + LAYOUTSHUTDOWN = OptionsMenuShutdown; +ENDLAYOUTBLOCK +WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 0 0, + BOTTOMRIGHT: 799 599, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "OptionsMenuSystem"; + INPUTCALLBACK = "OptionsMenuInput"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 2 2 2 126, BORDERCOLOR: 2 2 2 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 120 12, + BOTTOMRIGHT: 661 597, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:OptionsMenuParent"; + STATUS = ENABLED+NOFOCUS+SEE_THRU; + STYLE = USER; + SYSTEMCALLBACK = "OptionsMenuSystem"; + INPUTCALLBACK = "OptionsMenuInput"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 200 45, + BOTTOMRIGHT: 599 556, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:OptionsMenuParentOld"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 255 255 255 255, + DISABLED: 255 255 255 255, DISABLEDBORDER: 255 255 255 255, + HILITE: 255 255 255 255, HILITEBORDER: 255 255 255 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 2 2 2 175, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 128 128 255 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + CHILD + WINDOW + WINDOWTYPE = RADIOBUTTON; + SCREENRECT = UPPERLEFT: 135 215, + BOTTOMRIGHT: 279 247, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:RadioMedium"; + STATUS = ENABLED+HIDDEN+IMAGE+BORDER; + STYLE = RADIOBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Medium"; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: RadioButtonEnabledLeft, COLOR: 1 1 1 160, BORDERCOLOR: 47 55 168 255, + IMAGE: RadioButtonEnabledMiddle, COLOR: 128 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: RadioButtonEnabledRight, COLOR: 117 43 1 200, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: RadioButtonDisabledLeft, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: RadioButtonDisabledMiddle, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: RadioButtonDisabledRight, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: RadioButtonEnabledLeft, COLOR: 1 1 1 160, BORDERCOLOR: 47 55 168 255, + IMAGE: RadioButtonEnabledMiddle, COLOR: 128 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: RadioButtonEnabledRight, COLOR: 117 43 1 200, BORDERCOLOR: 128 128 255 255, + IMAGE: RadioButtonHilightedLeft, COLOR: 0 1 0 160, BORDERCOLOR: 47 55 168 255, + IMAGE: RadioButtonHilightedMiddle, COLOR: 0 128 0 0, BORDERCOLOR: 128 255 128 255, + IMAGE: RadioButtonHilightedRight, COLOR: 117 43 0 200, BORDERCOLOR: 254 254 254 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + RADIOBUTTONDATA = GROUP: 2; + END + CHILD + WINDOW + WINDOWTYPE = RADIOBUTTON; + SCREENRECT = UPPERLEFT: 135 183, + BOTTOMRIGHT: 279 215, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:RadioHigh"; + STATUS = ENABLED+HIDDEN+IMAGE+BORDER; + STYLE = RADIOBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXT = "GUI:High"; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: RadioButtonEnabledLeft, COLOR: 1 1 1 160, BORDERCOLOR: 47 55 168 255, + IMAGE: RadioButtonEnabledMiddle, COLOR: 128 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: RadioButtonEnabledRight, COLOR: 117 43 1 200, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: RadioButtonDisabledLeft, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: RadioButtonDisabledMiddle, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: RadioButtonDisabledRight, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: RadioButtonEnabledLeft, COLOR: 1 1 1 160, BORDERCOLOR: 47 55 168 255, + IMAGE: RadioButtonEnabledMiddle, COLOR: 128 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: RadioButtonEnabledRight, COLOR: 117 43 1 200, BORDERCOLOR: 128 128 255 255, + IMAGE: RadioButtonHilightedLeft, COLOR: 0 1 0 160, BORDERCOLOR: 47 55 168 255, + IMAGE: RadioButtonHilightedMiddle, COLOR: 0 128 0 0, BORDERCOLOR: 128 255 128 255, + IMAGE: RadioButtonHilightedRight, COLOR: 117 43 0 200, BORDERCOLOR: 254 254 254 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + RADIOBUTTONDATA = GROUP: 2; + END + CHILD + WINDOW + WINDOWTYPE = RADIOBUTTON; + SCREENRECT = UPPERLEFT: 135 247, + BOTTOMRIGHT: 279 279, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:RadioLow"; + STATUS = ENABLED+HIDDEN+IMAGE+BORDER; + STYLE = RADIOBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Low"; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: RadioButtonEnabledLeft, COLOR: 1 1 1 160, BORDERCOLOR: 47 55 168 255, + IMAGE: RadioButtonEnabledMiddle, COLOR: 128 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: RadioButtonEnabledRight, COLOR: 117 43 1 200, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: RadioButtonDisabledLeft, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: RadioButtonDisabledMiddle, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: RadioButtonDisabledRight, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: RadioButtonEnabledLeft, COLOR: 1 1 1 160, BORDERCOLOR: 47 55 168 255, + IMAGE: RadioButtonEnabledMiddle, COLOR: 128 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: RadioButtonEnabledRight, COLOR: 117 43 1 200, BORDERCOLOR: 128 128 255 255, + IMAGE: RadioButtonHilightedLeft, COLOR: 0 1 0 160, BORDERCOLOR: 47 55 168 255, + IMAGE: RadioButtonHilightedMiddle, COLOR: 0 128 0 0, BORDERCOLOR: 128 255 128 255, + IMAGE: RadioButtonHilightedRight, COLOR: 117 43 0 200, BORDERCOLOR: 254 254 254 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + RADIOBUTTONDATA = GROUP: 2; + END + CHILD + WINDOW + WINDOWTYPE = PUSHBUTTON; + SCREENRECT = UPPERLEFT: 465 512, + BOTTOMRIGHT: 587 544, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ButtonBack"; + STATUS = ENABLED+IMAGE; + STYLE = PUSHBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Generals", SIZE: 15, BOLD: 0; + HEADERTEMPLATE = "MainButton"; + TOOLTIPTEXT = "TOOLTIP:OptionsCancel"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Cancel"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: Buttons-Left, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: Buttons-Disabled-Left, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: Buttons-HiLite-Left, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = PUSHBUTTON; + SCREENRECT = UPPERLEFT: 338 512, + BOTTOMRIGHT: 461 544, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ButtonAccept"; + STATUS = ENABLED+IMAGE; + STYLE = PUSHBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Generals", SIZE: 15, BOLD: 0; + HEADERTEMPLATE = "MainButton"; + TOOLTIPTEXT = "TOOLTIP:OptionsAccept"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Accept"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: Buttons-Left, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: Buttons-Disabled-Left, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: Buttons-HiLite-Left, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = PUSHBUTTON; + SCREENRECT = UPPERLEFT: 212 512, + BOTTOMRIGHT: 334 544, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ButtonDefaults"; + STATUS = ENABLED+IMAGE; + STYLE = PUSHBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Generals", SIZE: 15, BOLD: 0; + HEADERTEMPLATE = "MainButton"; + TOOLTIPTEXT = "TOOLTIP:OptionsDefaults"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Defaults"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: Buttons-Left, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: Buttons-Disabled-Left, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: Buttons-HiLite-Left, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 212 377, + BOTTOMRIGHT: 587 502, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:NetworkParent"; + STATUS = ENABLED+NOFOCUS; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 2 255, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 128 128 255 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 217 377, + BOTTOMRIGHT: 423 401, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED+WRAP_CENTERED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 12, BOLD: 0; + HEADERTEMPLATE = "Button"; + TOOLTIPDELAY = -1; + TEXT = "GUI:NetworkOptions"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = PUSHBUTTON; + SCREENRECT = UPPERLEFT: 427 381, + BOTTOMRIGHT: 499 397, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ButtonTabOnline"; + STATUS = ENABLED+IMAGE+CHECK_LIKE; + STYLE = PUSHBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "ButtonSmall"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: Buttons-Left, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: Buttons-Disabled-Left, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: Buttons-HiLite-Left, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = PUSHBUTTON; + SCREENRECT = UPPERLEFT: 503 381, + BOTTOMRIGHT: 575 397, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ButtonTabLan"; + STATUS = ENABLED+IMAGE+CHECK_LIKE; + STYLE = PUSHBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "ButtonSmall"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: Buttons-Left, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: Buttons-Disabled-Left, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: Buttons-HiLite-Left, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 212 402, + BOTTOMRIGHT: 587 502, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:PanelOnline"; + STATUS = ENABLED+NOFOCUS; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 2 0, BORDERCOLOR: 47 55 168 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 0, BORDERCOLOR: 254 254 254 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 128 128 255 0, BORDERCOLOR: 254 254 254 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 217 402, + BOTTOMRIGHT: 361 422, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:StaticTextProxyMode"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 408 402, + BOTTOMRIGHT: 552 422, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:StaticTextProxyAddress"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = ENTRYFIELD; + SCREENRECT = UPPERLEFT: 415 422, + BOTTOMRIGHT: 575 446, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:TextEntryProxyAddress"; + STATUS = ENABLED+IMAGE; + STYLE = ENTRYFIELD+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "TextEntry"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: TextEntryEnabledLeftEnd, COLOR: 0 0 0 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryEnabledRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: TextEntryDisabledLeftEnd, COLOR: 0 0 128 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryDisabledRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: TextEntryHiliteLeftEnd, COLOR: 0 0 0 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryHiliteRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + TEXTENTRYDATA = MAXLEN: 128, + SECRETTEXT: 0, + NUMERICALONLY: 0, + ALPHANUMERICALONLY: 0, + ASCIIONLY: 1; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 217 450, + BOTTOMRIGHT: 361 470, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:StaticTextServer"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 408 450, + BOTTOMRIGHT: 552 470, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:StaticTextServerAddress"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = ENTRYFIELD; + SCREENRECT = UPPERLEFT: 415 470, + BOTTOMRIGHT: 575 494, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:TextEntryServerAddress"; + STATUS = ENABLED+IMAGE; + STYLE = ENTRYFIELD+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "TextEntry"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: TextEntryEnabledLeftEnd, COLOR: 0 0 0 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryEnabledRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: TextEntryDisabledLeftEnd, COLOR: 0 0 128 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryDisabledRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: TextEntryHiliteLeftEnd, COLOR: 0 0 0 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryHiliteRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + TEXTENTRYDATA = MAXLEN: 128, + SECRETTEXT: 0, + NUMERICALONLY: 0, + ALPHANUMERICALONLY: 0, + ASCIIONLY: 1; + END + CHILD + WINDOW + WINDOWTYPE = COMBOBOX; + SCREENRECT = UPPERLEFT: 224 470, + BOTTOMRIGHT: 387 494, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ComboBoxServer"; + STATUS = ENABLED+IMAGE; + STYLE = MOUSETRACK+COMBOBOX; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "ComboBoxEntry"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDATA = ISEDITABLE: 0, + MAXCHARS: 16, + MAXDISPLAY: 5, + ASCIIONLY: 0, + LETTERSANDNUMBERS: 0; + COMBOBOXDROPDOWNBUTTONENABLEDDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONDISABLEDDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONHILITEDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXENABLEDDRAWDATA = IMAGE: TextEntryEnabledLeftEnd, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: TextEntryEnabledRightEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryEnabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXDISABLEDDRAWDATA = IMAGE: TextEntryDisabledLeftEnd, COLOR: 128 128 128 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryDisabledRightEnd, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryDisabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXHILITEDRAWDATA = IMAGE: TextEntryHiliteLeftEnd, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRightEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXENABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteItemLeftEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: ListBoxHiliteItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXDISABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXHILITEDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 168 255 12 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 0, BORDERCOLOR: 148 112 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITESLIDERDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 0, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: ScrollBarThumbEnabled, COLOR: 255 4 0 0, BORDERCOLOR: 255 243 28 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: ScrollBarThumbDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: ScrollBarThumbHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = COMBOBOX; + SCREENRECT = UPPERLEFT: 224 422, + BOTTOMRIGHT: 387 446, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ComboBoxProxyMode"; + STATUS = ENABLED+IMAGE; + STYLE = MOUSETRACK+COMBOBOX; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "ComboBoxEntry"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDATA = ISEDITABLE: 0, + MAXCHARS: 16, + MAXDISPLAY: 5, + ASCIIONLY: 0, + LETTERSANDNUMBERS: 0; + COMBOBOXDROPDOWNBUTTONENABLEDDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONDISABLEDDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONHILITEDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXENABLEDDRAWDATA = IMAGE: TextEntryEnabledLeftEnd, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: TextEntryEnabledRightEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryEnabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXDISABLEDDRAWDATA = IMAGE: TextEntryDisabledLeftEnd, COLOR: 128 128 128 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryDisabledRightEnd, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryDisabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXHILITEDRAWDATA = IMAGE: TextEntryHiliteLeftEnd, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRightEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXENABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteItemLeftEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: ListBoxHiliteItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXDISABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXHILITEDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 168 255 12 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 0, BORDERCOLOR: 148 112 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITESLIDERDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 0, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: ScrollBarThumbEnabled, COLOR: 255 4 0 0, BORDERCOLOR: 255 243 28 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: ScrollBarThumbDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: ScrollBarThumbHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + ENDALLCHILDREN + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 212 402, + BOTTOMRIGHT: 587 502, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:PanelLan"; + STATUS = ENABLED+NOFOCUS; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 2 0, BORDERCOLOR: 47 55 168 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 0, BORDERCOLOR: 254 254 254 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 128 128 255 0, BORDERCOLOR: 254 254 254 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 217 402, + BOTTOMRIGHT: 361 422, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:StaticTextLANIpAddresses"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = COMBOBOX; + SCREENRECT = UPPERLEFT: 224 422, + BOTTOMRIGHT: 387 446, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ComboBoxIP"; + STATUS = ENABLED+IMAGE; + STYLE = MOUSETRACK+COMBOBOX; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "ComboBoxEntry"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDATA = ISEDITABLE: 0, + MAXCHARS: 16, + MAXDISPLAY: 5, + ASCIIONLY: 0, + LETTERSANDNUMBERS: 0; + COMBOBOXDROPDOWNBUTTONENABLEDDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONDISABLEDDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONHILITEDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXENABLEDDRAWDATA = IMAGE: TextEntryEnabledLeftEnd, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: TextEntryEnabledRightEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryEnabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXDISABLEDDRAWDATA = IMAGE: TextEntryDisabledLeftEnd, COLOR: 128 128 128 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryDisabledRightEnd, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryDisabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXHILITEDRAWDATA = IMAGE: TextEntryHiliteLeftEnd, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRightEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXENABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteItemLeftEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: ListBoxHiliteItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXDISABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXHILITEDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 168 255 12 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 0, BORDERCOLOR: 148 112 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITESLIDERDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 0, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: ScrollBarThumbEnabled, COLOR: 255 4 0 0, BORDERCOLOR: 255 243 28 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: ScrollBarThumbDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: ScrollBarThumbHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + ENDALLCHILDREN + END + ENDALLCHILDREN + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 731 59, + BOTTOMRIGHT: 883 87, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED+HIDDEN; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXT = "GUI:MouseType"; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 1; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 212 89, + BOTTOMRIGHT: 399 247, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:VideoParent"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 255 255 255 255, + DISABLED: 255 255 255 255, DISABLEDBORDER: 255 255 255 255, + HILITE: 255 255 255 255, HILITEBORDER: 255 255 255 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 128 128 255 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + CHILD + WINDOW + WINDOWTYPE = HORZSLIDER; + SCREENRECT = UPPERLEFT: 224 230, + BOTTOMRIGHT: 387 242, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:SliderGamma"; + STATUS = ENABLED+IMAGE+TABSTOP; + STYLE = HORZSLIDER+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[None]"; + TOOLTIPTEXT = "TOOLTIP:Brightness"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: hilightedbox, COLOR: 128 128 128 255, BORDERCOLOR: 64 64 64 255, + IMAGE: dehilightedbox, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: linebox, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: arrow, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERDATA = MINVALUE: 0, + MAXVALUE: 100; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 128 128 255, BORDERCOLOR: 255 0 0 255, + IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 64 64 64 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: arrow, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: arrow, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 217 210, + BOTTOMRIGHT: 393 230, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:GammaLabel"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXT = "GUI:GammaAdjustment"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = COMBOBOX; + SCREENRECT = UPPERLEFT: 199 230, + BOTTOMRIGHT: 339 258, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ComboBoxAntiAliasing"; + STATUS = ENABLED+HIDDEN+IMAGE; + STYLE = MOUSETRACK+COMBOBOX; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "ComboBoxEntry"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDATA = ISEDITABLE: 0, + MAXCHARS: 16, + MAXDISPLAY: 5, + ASCIIONLY: 0, + LETTERSANDNUMBERS: 0; + COMBOBOXDROPDOWNBUTTONENABLEDDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONDISABLEDDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONHILITEDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXENABLEDDRAWDATA = IMAGE: TextEntryEnabledLeftEnd, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: TextEntryEnabledRightEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryEnabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXDISABLEDDRAWDATA = IMAGE: TextEntryDisabledLeftEnd, COLOR: 128 128 128 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryDisabledRightEnd, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryDisabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXHILITEDRAWDATA = IMAGE: TextEntryHiliteLeftEnd, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRightEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXENABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteItemLeftEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: ListBoxHiliteItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXDISABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXHILITEDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 168 255 12 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 0, BORDERCOLOR: 148 112 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITESLIDERDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 0, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: ScrollBarThumbEnabled, COLOR: 255 4 0 0, BORDERCOLOR: 255 243 28 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: ScrollBarThumbDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: ScrollBarThumbHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 179 201, + BOTTOMRIGHT: 343 229, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED+HIDDEN; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "MinorTitle"; + TOOLTIPDELAY = -1; + TEXT = "GUI:AntiAliasing"; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 1; + END + CHILD + WINDOW + WINDOWTYPE = COMBOBOX; + SCREENRECT = UPPERLEFT: 224 182, + BOTTOMRIGHT: 387 206, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ComboBoxDetail"; + STATUS = ENABLED+IMAGE; + STYLE = MOUSETRACK+COMBOBOX; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "ComboBoxEntry"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDATA = ISEDITABLE: 0, + MAXCHARS: 16, + MAXDISPLAY: 5, + ASCIIONLY: 0, + LETTERSANDNUMBERS: 0; + COMBOBOXDROPDOWNBUTTONENABLEDDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONDISABLEDDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONHILITEDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXENABLEDDRAWDATA = IMAGE: TextEntryEnabledLeftEnd, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: TextEntryEnabledRightEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryEnabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXDISABLEDDRAWDATA = IMAGE: TextEntryDisabledLeftEnd, COLOR: 128 128 128 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryDisabledRightEnd, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryDisabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXHILITEDRAWDATA = IMAGE: TextEntryHiliteLeftEnd, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRightEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXENABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteItemLeftEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: ListBoxHiliteItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXDISABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXHILITEDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 168 255 12 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 0, BORDERCOLOR: 148 112 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITESLIDERDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 0, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: ScrollBarThumbEnabled, COLOR: 255 4 0 0, BORDERCOLOR: 255 243 28 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: ScrollBarThumbDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: ScrollBarThumbHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 217 162, + BOTTOMRIGHT: 361 182, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:DetailLabel"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Detail"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = COMBOBOX; + SCREENRECT = UPPERLEFT: 224 134, + BOTTOMRIGHT: 387 158, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ComboBoxResolution"; + STATUS = ENABLED+IMAGE; + STYLE = MOUSETRACK+COMBOBOX; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "ComboBoxEntry"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: ListBoxHiliteItemLeftEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: ListBoxHiliteItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDATA = ISEDITABLE: 0, + MAXCHARS: 16, + MAXDISPLAY: 5, + ASCIIONLY: 0, + LETTERSANDNUMBERS: 0; + COMBOBOXDROPDOWNBUTTONENABLEDDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONDISABLEDDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXDROPDOWNBUTTONHILITEDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXENABLEDDRAWDATA = IMAGE: TextEntryEnabledLeftEnd, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: TextEntryEnabledRightEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryEnabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXDISABLEDDRAWDATA = IMAGE: TextEntryDisabledLeftEnd, COLOR: 128 128 128 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryDisabledRightEnd, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: TextEntryDisabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXEDITBOXHILITEDRAWDATA = IMAGE: TextEntryHiliteLeftEnd, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRightEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: TextEntryHiliteRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXENABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteItemLeftEnd, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: ListBoxHiliteItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXDISABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + COMBOBOXLISTBOXHILITEDRAWDATA = IMAGE: BlackSquare, COLOR: 0 0 0 255, BORDERCOLOR: 49 55 168 255, + IMAGE: ListBoxHiliteSelectedItemLeftEnd, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ListBoxHiliteSelectedItemRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: ListBoxHiliteSelectedItemSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEUPBUTTONDRAWDATA = IMAGE: VSliderUpButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderUpButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonEnabled, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITEDOWNBUTTONDRAWDATA = IMAGE: VSliderDownButtonHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: VSliderDownButtonHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXENABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 168 255 12 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXDISABLEDSLIDERDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 0, BORDERCOLOR: 148 112 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + LISTBOXHILITESLIDERDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 0, BORDERCOLOR: 49 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: ScrollBarThumbEnabled, COLOR: 255 4 0 0, BORDERCOLOR: 255 243 28 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: ScrollBarThumbDisabled, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: ScrollBarThumbHilite, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: ScrollBarThumbHiliteSelected, COLOR: 254 254 254 255, BORDERCOLOR: 0 128 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 217 114, + BOTTOMRIGHT: 361 134, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ResolutionLabel"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Resolution"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 217 89, + BOTTOMRIGHT: 368 113, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED+WRAP_CENTERED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 12, BOLD: 0; + HEADERTEMPLATE = "Button"; + TOOLTIPDELAY = -1; + TEXT = "GUI:DisplayOptions"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + ENDALLCHILDREN + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 212 257, + BOTTOMRIGHT: 587 367, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ScrollParent"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 2 255, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 128 128 255 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 217 257, + BOTTOMRIGHT: 587 281, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 12, BOLD: 0; + HEADERTEMPLATE = "Button"; + TOOLTIPDELAY = -1; + TEXT = "GUI:ControlOptions"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 224 282, + BOTTOMRIGHT: 387 306, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckAlternateMouse"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:AlternateMouse"; + TOOLTIPDELAY = -1; + TEXT = "GUI:AlternateMouse"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 217 330, + BOTTOMRIGHT: 577 350, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ScrollSpeedLabel"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXT = "GUI:ScrollSpeed"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = HORZSLIDER; + SCREENRECT = UPPERLEFT: 224 350, + BOTTOMRIGHT: 575 362, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:SliderScrollSpeed"; + STATUS = ENABLED+IMAGE+TABSTOP; + STYLE = HORZSLIDER+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[None]"; + TOOLTIPTEXT = "ToolTip:ScrollSpeed"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 0 0 0 0, ENABLEDBORDER: 0 0 0 0, + DISABLED: 0 0 0 0, DISABLEDBORDER: 0 0 0 0, + HILITE: 0 0 0 0, HILITEBORDER: 0 0 0 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: hilightedbox, COLOR: 128 128 128 255, BORDERCOLOR: 64 64 64 255, + IMAGE: dehilightedbox, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: linebox, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: arrow, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERDATA = MINVALUE: 10, + MAXVALUE: 300; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 128 128 255, BORDERCOLOR: 255 0 0 255, + IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 64 64 64 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: arrow, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: arrow, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 415 282, + BOTTOMRIGHT: 575 306, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckDoubleClickAttackMove"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:DoubleClickAttackMove"; + TOOLTIPDELAY = -1; + TEXT = "GUI:DoubleClickAttackMove"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 224 306, + BOTTOMRIGHT: 387 330, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:Retaliation"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "GUI:Retaliation"; + TOOLTIPDELAY = -1; + TEXT = "GUI:RetaliationTitle"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + ENDALLCHILDREN + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 723 155, + BOTTOMRIGHT: 935 191, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckLanguageFilter"; + STATUS = ENABLED+HIDDEN+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "GUI:LanguageFilter"; + TOOLTIPDELAY = -1; + TEXT = "GUI:LanguageFilter"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 403 89, + BOTTOMRIGHT: 587 247, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:AudioParent"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 255 255 255 255, + DISABLED: 255 255 255 255, DISABLEDBORDER: 255 255 255 255, + HILITE: 255 255 255 255, HILITEBORDER: 255 255 255 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 128 128 255 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + CHILD + WINDOW + WINDOWTYPE = HORZSLIDER; + SCREENRECT = UPPERLEFT: 415 230, + BOTTOMRIGHT: 575 242, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:SliderVoiceVolume"; + STATUS = ENABLED+IMAGE+TABSTOP; + STYLE = HORZSLIDER+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[None]"; + TOOLTIPTEXT = "GUI:VoiceVolume"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: hilightedbox, COLOR: 128 128 128 255, BORDERCOLOR: 64 64 64 255, + IMAGE: dehilightedbox, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: linebox, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: arrow, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERDATA = MINVALUE: 0, + MAXVALUE: 100; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 128 128 255, BORDERCOLOR: 255 0 0 255, + IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 64 64 64 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: arrow, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: arrow, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 408 210, + BOTTOMRIGHT: 582 230, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:VoiceVolumeLabel"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "GUI:VoiceVolume"; + TOOLTIPDELAY = -1; + TEXT = "GUI:VoiceVolume"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 408 114, + BOTTOMRIGHT: 582 134, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:MusicVolumeLabel"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "GUI:MusicVolume"; + TOOLTIPDELAY = -1; + TEXT = "GUI:MusicVolume"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 408 162, + BOTTOMRIGHT: 582 182, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:SFXVolumeLabel"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "GUI:SFXVolume"; + TOOLTIPDELAY = -1; + TEXT = "GUI:SFXVolume"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 408 89, + BOTTOMRIGHT: 563 113, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED+WRAP_CENTERED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 12, BOLD: 0; + HEADERTEMPLATE = "Button"; + TOOLTIPDELAY = -1; + TEXT = "GUI:AudioOptions"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + ENDALLCHILDREN + END + CHILD + WINDOW + WINDOWTYPE = HORZSLIDER; + SCREENRECT = UPPERLEFT: 415 134, + BOTTOMRIGHT: 575 146, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:SliderMusicVolume"; + STATUS = ENABLED+IMAGE+TABSTOP; + STYLE = HORZSLIDER+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[None]"; + TOOLTIPTEXT = "GUI:MusicVolume"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 0 0 0 0, ENABLEDBORDER: 0 0 0 0, + DISABLED: 0 0 0 0, DISABLEDBORDER: 0 0 0 0, + HILITE: 0 0 0 0, HILITEBORDER: 0 0 0 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: hilightedbox, COLOR: 128 128 128 255, BORDERCOLOR: 64 64 64 255, + IMAGE: dehilightedbox, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: linebox, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: arrow, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERDATA = MINVALUE: 0, + MAXVALUE: 100; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 128 128 255, BORDERCOLOR: 255 0 0 255, + IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 64 64 64 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: arrow, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: arrow, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + END + CHILD + WINDOW + WINDOWTYPE = HORZSLIDER; + SCREENRECT = UPPERLEFT: 415 182, + BOTTOMRIGHT: 575 194, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:SliderSFXVolume"; + STATUS = ENABLED+IMAGE+TABSTOP; + STYLE = HORZSLIDER+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[None]"; + TOOLTIPTEXT = "GUI:SFXVolume"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: hilightedbox, COLOR: 128 128 128 255, BORDERCOLOR: 64 64 64 255, + IMAGE: dehilightedbox, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: linebox, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: arrow, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERDATA = MINVALUE: 0, + MAXVALUE: 100; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 128 128 255, BORDERCOLOR: 255 0 0 255, + IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 64 64 64 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: arrow, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: arrow, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + END + CHILD + WINDOW + WINDOWTYPE = PUSHBUTTON; + SCREENRECT = UPPERLEFT: 747 127, + BOTTOMRIGHT: 923 167, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ButtonKeyboardOptions"; + STATUS = ENABLED+HIDDEN+IMAGE; + STYLE = PUSHBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXT = "GUI:KeyboardOptions"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: Buttons-Left, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: Buttons-Disabled-Left, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: Buttons-HiLite-Left, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 647 487, + BOTTOMRIGHT: 919 511, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckBoxSaveCamera"; + STATUS = ENABLED+HIDDEN+IMAGE; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXT = "GUI:SaveCamera"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 647 511, + BOTTOMRIGHT: 919 535, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckBoxUseCamera"; + STATUS = ENABLED+HIDDEN+IMAGE; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXT = "GUI:UseCamera"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 647 535, + BOTTOMRIGHT: 919 559, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckBoxDrawAnchor"; + STATUS = ENABLED+HIDDEN+IMAGE; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXT = "GUI:DrawScrollAnchor"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 647 559, + BOTTOMRIGHT: 919 583, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckBoxMoveAnchor"; + STATUS = ENABLED+HIDDEN+IMAGE; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXT = "GUI:MoveScrollAnchor"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 200 77, + BOTTOMRIGHT: 599 78, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:Line"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 217 47, + BOTTOMRIGHT: 481 77, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Generals", SIZE: 20, BOLD: 0; + HEADERTEMPLATE = "Title"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Options"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 2 2 2 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 403 113, + BOTTOMRIGHT: 587 114, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:Line2"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 212 281, + BOTTOMRIGHT: 587 282, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:Line3"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 212 401, + BOTTOMRIGHT: 587 402, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:Line4"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 212 113, + BOTTOMRIGHT: 399 114, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:Line1"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 151 68, + BOTTOMRIGHT: 636 560, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:WinAdvancedDisplayOptions"; + STATUS = ENABLED+HIDDEN; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 151 105, + BOTTOMRIGHT: 636 106, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:AdvanceOptionsLine"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = PUSHBUTTON; + SCREENRECT = UPPERLEFT: 295 521, + BOTTOMRIGHT: 454 553, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ButtonAdvanceAccept"; + STATUS = ENABLED+IMAGE; + STYLE = PUSHBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Generals", SIZE: 15, BOLD: 0; + HEADERTEMPLATE = "MainButton"; + TOOLTIPTEXT = "TOOLTIP:OptionsAccept"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Accept"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: Buttons-Left, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: Buttons-Disabled-Left, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: Buttons-HiLite-Left, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = PUSHBUTTON; + SCREENRECT = UPPERLEFT: 466 521, + BOTTOMRIGHT: 625 553, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ButtonAdvanceBack"; + STATUS = ENABLED+IMAGE; + STYLE = PUSHBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Generals", SIZE: 15, BOLD: 0; + HEADERTEMPLATE = "MainButton"; + TOOLTIPTEXT = "TOOLTIP:OptionsCancel"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Cancel"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: Buttons-Left, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: Buttons-Disabled-Left, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: Buttons-HiLite-Left, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 162 74, + BOTTOMRIGHT: 576 102, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED+WRAP_CENTERED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "MinorTitle"; + TOOLTIPDELAY = -1; + TEXT = "GUI:AdvanceDisplayOptions"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 160 112, + BOTTOMRIGHT: 622 301, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 168 144, + BOTTOMRIGHT: 368 169, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:Check3DShadows"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:Shadows3D"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Shadows3D"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 384 168, + BOTTOMRIGHT: 615 192, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckShowProps"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:ShowProps"; + TOOLTIPDELAY = -1; + TEXT = "GUI:ShowProps"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 384 144, + BOTTOMRIGHT: 614 168, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckBehindBuilding"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:BehindBuilding"; + TOOLTIPDELAY = -1; + TEXT = "GUI:BehindBuilding"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 168 168, + BOTTOMRIGHT: 368 193, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:Check2DShadows"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:Shadows2D"; + TOOLTIPDELAY = -1; + TEXT = "GUI:Shadows2D"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 168 216, + BOTTOMRIGHT: 368 240, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckGroundLighting"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:GroundLighting"; + TOOLTIPDELAY = -1; + TEXT = "GUI:GroundLighting"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 384 216, + BOTTOMRIGHT: 615 240, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckNoDynamicLOD"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:NoDynamicLOD"; + TOOLTIPDELAY = -1; + TEXT = "GUI:NoDynamicLOD"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 384 240, + BOTTOMRIGHT: 615 264, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckHeatEffects"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:HeatEffects"; + TOOLTIPDELAY = -1; + TEXT = "GUI:HeatEffects"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 384 264, + BOTTOMRIGHT: 615 288, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckUnlockFPS"; + STATUS = ENABLED+HIDDEN+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:UnlockMaxFPS"; + TOOLTIPDELAY = -1; + TEXT = "GUI:UnlockMaxFPS"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 168 192, + BOTTOMRIGHT: 368 216, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckCloudShadows"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:CloudShadows"; + TOOLTIPDELAY = -1; + TEXT = "GUI:CloudShadows"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 384 192, + BOTTOMRIGHT: 616 216, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckExtraAnimations"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:ExtraAnimations"; + TOOLTIPDELAY = -1; + TEXT = "GUI:ExtraAnimations"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 168 240, + BOTTOMRIGHT: 368 265, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:CheckSmoothWater"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPTEXT = "TOOLTIP:SmoothWater"; + TOOLTIPDELAY = -1; + TEXT = "GUI:SmoothWater"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 160 139, + BOTTOMRIGHT: 622 140, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ToggleOnOffLine"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 167 112, + BOTTOMRIGHT: 607 140, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED+WRAP_CENTERED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "MinorTitle"; + TOOLTIPDELAY = -1; + TEXT = "GUI:AdvancedOptionsToggleOnOff"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + ENDALLCHILDREN + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 160 312, + BOTTOMRIGHT: 625 399, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:LowResWin"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 172 315, + BOTTOMRIGHT: 461 339, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "MinorTitle"; + TOOLTIPDELAY = -1; + TEXT = "GUI:LowResSlider"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = HORZSLIDER; + SCREENRECT = UPPERLEFT: 240 355, + BOTTOMRIGHT: 526 379, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:LowResSlider"; + STATUS = ENABLED+IMAGE+TABSTOP; + STYLE = HORZSLIDER+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[None]"; + TOOLTIPTEXT = "TOOLTIP:LowResSlider"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: hilightedbox, COLOR: 128 128 128 255, BORDERCOLOR: 64 64 64 255, + IMAGE: dehilightedbox, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: linebox, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: arrow, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERDATA = MINVALUE: 0, + MAXVALUE: 2; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 128 128 255, BORDERCOLOR: 255 0 0 255, + IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 64 64 64 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: arrow, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: arrow, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 427 375, + BOTTOMRIGHT: 625 399, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXT = "GUI:HighResSlower"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 160 342, + BOTTOMRIGHT: 625 343, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:LowResLine"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 160 375, + BOTTOMRIGHT: 373 399, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXT = "GUI:LowResFaster"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + ENDALLCHILDREN + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 160 416, + BOTTOMRIGHT: 625 503, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ParticleCapWin"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 172 419, + BOTTOMRIGHT: 461 443, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "MinorTitle"; + TOOLTIPDELAY = -1; + TEXT = "GUI:ParticleCap"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 160 476, + BOTTOMRIGHT: 373 500, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXT = "GUI:LessParticlesFaster"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 412 476, + BOTTOMRIGHT: 625 500, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXT = "GUI:MoreParticlesSlower"; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 4 0 0, BORDERCOLOR: 255 132 130 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 128 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 160 445, + BOTTOMRIGHT: 625 446, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ParticleCapLine"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 0, ENABLEDBORDER: 255 255 255 0, + DISABLED: 255 255 255 0, DISABLEDBORDER: 255 255 255 0, + HILITE: 255 255 255 0, HILITEBORDER: 255 255 255 0; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 0 255 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = HORZSLIDER; + SCREENRECT = UPPERLEFT: 240 459, + BOTTOMRIGHT: 526 483, + CREATIONRESOLUTION: 800 600; + NAME = "OptionsMenu.wnd:ParticleCapSlider"; + STATUS = ENABLED+IMAGE+TABSTOP; + STYLE = HORZSLIDER+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[None]"; + TOOLTIPTEXT = "TOOLTIP:ParticleCapSlider"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 128 128 255 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: hilightedbox, COLOR: 128 128 128 255, BORDERCOLOR: 64 64 64 255, + IMAGE: dehilightedbox, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: linebox, COLOR: 0 255 0 255, BORDERCOLOR: 0 128 0 255, + IMAGE: arrow, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERDATA = MINVALUE: 100, + MAXVALUE: 5000; + SLIDERTHUMBENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 128 128 255, BORDERCOLOR: 255 0 0 255, + IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBDISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 0 0 0 255, BORDERCOLOR: 64 64 64 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + SLIDERTHUMBHILITEDRAWDATA = IMAGE: arrow, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: arrow, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + END + ENDALLCHILDREN + END + ENDALLCHILDREN + END + ENDALLCHILDREN + END + ENDALLCHILDREN + END + ENDALLCHILDREN +END diff --git a/GeneralsOnline/GameData/Window/Menus/ProxyLogin.wnd b/GeneralsOnline/GameData/Window/Menus/ProxyLogin.wnd new file mode 100644 index 00000000000..71c63c97175 --- /dev/null +++ b/GeneralsOnline/GameData/Window/Menus/ProxyLogin.wnd @@ -0,0 +1,779 @@ +FILE_VERSION = 2; +STARTLAYOUTBLOCK + LAYOUTINIT = [None]; + LAYOUTUPDATE = [None]; + LAYOUTSHUTDOWN = [None]; +ENDLAYOUTBLOCK +WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 0 0, + BOTTOMRIGHT: 799 599, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:ProxyLoginRoot"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 2 2 2 115, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 270 173, + BOTTOMRIGHT: 529 431, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:ProxyLoginParent"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "PassMessagesToParentSystem"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: BlackSquare, COLOR: 2 2 2 255, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 272 173, + BOTTOMRIGHT: 527 201, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:StaticTextTitle"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Generals", SIZE: 15, BOLD: 0; + HEADERTEMPLATE = "MainButton"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 35 35 35 0, BORDERCOLOR: 0 0 0 0, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 285 209, + BOTTOMRIGHT: 357 232, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:StaticTextProxy"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 33 32 32 0, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = ENTRYFIELD; + SCREENRECT = UPPERLEFT: 361 209, + BOTTOMRIGHT: 514 232, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:TextEntryProxy"; + STATUS = ENABLED+IMAGE; + STYLE = ENTRYFIELD+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "TextEntry"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: TextEntryEnabledLeftEnd, COLOR: 0 0 0 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryEnabledRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: TextEntryDisabledLeftEnd, COLOR: 0 0 128 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryDisabledRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: TextEntryHiliteLeftEnd, COLOR: 0 0 0 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryHiliteRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + TEXTENTRYDATA = MAXLEN: 128, + SECRETTEXT: 0, + NUMERICALONLY: 0, + ALPHANUMERICALONLY: 0, + ASCIIONLY: 1; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 361 237, + BOTTOMRIGHT: 514 260, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:CheckBoxWindowsLogin"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 285 265, + BOTTOMRIGHT: 357 288, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:StaticTextUserName"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 33 32 32 0, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = ENTRYFIELD; + SCREENRECT = UPPERLEFT: 361 265, + BOTTOMRIGHT: 514 288, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:TextEntryUserName"; + STATUS = ENABLED+IMAGE; + STYLE = ENTRYFIELD+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "TextEntry"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: TextEntryEnabledLeftEnd, COLOR: 0 0 0 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryEnabledRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: TextEntryDisabledLeftEnd, COLOR: 0 0 128 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryDisabledRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: TextEntryHiliteLeftEnd, COLOR: 0 0 0 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryHiliteRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + TEXTENTRYDATA = MAXLEN: 128, + SECRETTEXT: 0, + NUMERICALONLY: 0, + ALPHANUMERICALONLY: 0, + ASCIIONLY: 1; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 285 293, + BOTTOMRIGHT: 357 316, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:StaticTextPassword"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 33 32 32 0, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 0; + END + CHILD + WINDOW + WINDOWTYPE = ENTRYFIELD; + SCREENRECT = UPPERLEFT: 361 293, + BOTTOMRIGHT: 514 316, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:TextEntryPassword"; + STATUS = ENABLED+IMAGE; + STYLE = ENTRYFIELD+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "TextEntry"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 254 254 254 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 168 255 12 255, HILITEBORDER: 0 0 128 255; + ENABLEDDRAWDATA = IMAGE: TextEntryEnabledLeftEnd, COLOR: 0 0 0 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryEnabledRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryEnabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: TextEntryDisabledLeftEnd, COLOR: 0 0 128 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryDisabledRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryDisabledSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: TextEntryHiliteLeftEnd, COLOR: 0 0 0 255, BORDERCOLOR: 0 0 0 255, + IMAGE: TextEntryHiliteRightEnd, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: TextEntryHiliteSmallRepeatingCenter, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + TEXTENTRYDATA = MAXLEN: 128, + SECRETTEXT: 1, + NUMERICALONLY: 0, + ALPHANUMERICALONLY: 0, + ASCIIONLY: 1; + END + CHILD + WINDOW + WINDOWTYPE = CHECKBOX; + SCREENRECT = UPPERLEFT: 361 321, + BOTTOMRIGHT: 514 344, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:CheckBoxRemember"; + STATUS = ENABLED+IMAGE+BORDER; + STYLE = CHECKBOX+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 192 192 192 255, DISABLEDBORDER: 64 64 64 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 0 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: Active-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 0 0 255 255, BORDERCOLOR: 128 128 255 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Unchecked, COLOR: 255 255 255 0, BORDERCOLOR: 192 192 192 255, + IMAGE: Disabled-Checked, COLOR: 64 64 64 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 0 255 0 255, BORDERCOLOR: 128 255 128 255, + IMAGE: Active-HiLighted, COLOR: 255 255 255 0, BORDERCOLOR: 128 128 255 255, + IMAGE: Active-Checked, COLOR: 255 255 0 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = STATICTEXT; + SCREENRECT = UPPERLEFT: 285 349, + BOTTOMRIGHT: 514 388, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:StaticTextMessage"; + STATUS = ENABLED; + STYLE = STATICTEXT+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Arial", SIZE: 10, BOLD: 0; + HEADERTEMPLATE = "LabelRegular"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 33 32 32 0, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 64 64 64 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255, + IMAGE: NoImage, COLOR: 255 255 255 255, BORDERCOLOR: 255 255 255 255; + STATICTEXTDATA = CENTERED: 1; + END + CHILD + WINDOW + WINDOWTYPE = PUSHBUTTON; + SCREENRECT = UPPERLEFT: 285 393, + BOTTOMRIGHT: 357 416, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:ButtonOk"; + STATUS = ENABLED+IMAGE; + STYLE = PUSHBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Generals", SIZE: 15, BOLD: 0; + HEADERTEMPLATE = "MainButton"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: Buttons-Left, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: Buttons-Disabled-Left, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: Buttons-HiLite-Left, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = PUSHBUTTON; + SCREENRECT = UPPERLEFT: 364 393, + BOTTOMRIGHT: 435 416, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:ButtonSave"; + STATUS = ENABLED+IMAGE; + STYLE = PUSHBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Generals", SIZE: 15, BOLD: 0; + HEADERTEMPLATE = "MainButton"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: Buttons-Left, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: Buttons-Disabled-Left, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: Buttons-HiLite-Left, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = PUSHBUTTON; + SCREENRECT = UPPERLEFT: 442 393, + BOTTOMRIGHT: 514 416, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:ButtonCancel"; + STATUS = ENABLED+IMAGE; + STYLE = PUSHBUTTON+MOUSETRACK; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Generals", SIZE: 15, BOLD: 0; + HEADERTEMPLATE = "MainButton"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: Buttons-Left, COLOR: 255 0 0 255, BORDERCOLOR: 255 128 128 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: Buttons-Disabled-Left, COLOR: 128 128 128 255, BORDERCOLOR: 192 192 192 255, + IMAGE: NoImage, COLOR: 192 192 192 255, BORDERCOLOR: 128 128 128 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Disabled-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: Buttons-HiLite-Left, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: Buttons-Pushed-Left, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-Pushed-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Middle, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: Buttons-HiLite-Right, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + CHILD + WINDOW + WINDOWTYPE = USER; + SCREENRECT = UPPERLEFT: 270 173, + BOTTOMRIGHT: 529 201, + CREATIONRESOLUTION: 800 600; + NAME = "ProxyLogin.wnd:Header"; + STATUS = ENABLED; + STYLE = USER; + SYSTEMCALLBACK = "[None]"; + INPUTCALLBACK = "[None]"; + TOOLTIPCALLBACK = "[None]"; + DRAWCALLBACK = "[None]"; + FONT = NAME: "Times New Roman", SIZE: 14, BOLD: 0; + HEADERTEMPLATE = "[NONE]"; + TOOLTIPDELAY = -1; + TEXTCOLOR = ENABLED: 255 255 255 255, ENABLEDBORDER: 0 0 0 255, + DISABLED: 62 64 92 255, DISABLEDBORDER: 31 32 47 255, + HILITE: 186 255 12 255, HILITEBORDER: 0 2 0 255; + ENABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 0 0, BORDERCOLOR: 47 55 168 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + DISABLEDDRAWDATA = IMAGE: NoImage, COLOR: 0 0 64 255, BORDERCOLOR: 0 0 0 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + HILITEDRAWDATA = IMAGE: NoImage, COLOR: 209 253 4 255, BORDERCOLOR: 59 60 52 255, + IMAGE: NoImage, COLOR: 47 55 168 255, BORDERCOLOR: 254 254 254 255, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0, + IMAGE: NoImage, COLOR: 255 255 255 0, BORDERCOLOR: 255 255 255 0; + END + ENDALLCHILDREN + END + ENDALLCHILDREN +END diff --git a/cmake/triplets/x86-windows-static-md.cmake b/cmake/triplets/x86-windows-static-md.cmake new file mode 100644 index 00000000000..824d4e3fd3f --- /dev/null +++ b/cmake/triplets/x86-windows-static-md.cmake @@ -0,0 +1,13 @@ +set(VCPKG_TARGET_ARCHITECTURE x86) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) + +# The pinned curl port exposes SSPI but leaves NTLM disabled without this option. +if(PORT STREQUAL "curl") + list(APPEND VCPKG_CMAKE_CONFIGURE_OPTIONS "-DCURL_ENABLE_NTLM=ON") +endif() + +# Match the static protobuf package instead of importing protobuf DLL symbols. +if(PORT STREQUAL "gamenetworkingsockets") + list(APPEND VCPKG_CMAKE_CONFIGURE_OPTIONS "-DProtobuf_USE_STATIC_LIBS=ON") +endif() diff --git a/scripts/pack_go_data.py b/scripts/pack_go_data.py new file mode 100644 index 00000000000..a084b052273 --- /dev/null +++ b/scripts/pack_go_data.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +r"""Pack GeneralsOnline/GameData into the Generals Online fix pack BIG. + +Usage: pack_go_data.py [output.big] + +The folder layout inside GameData is the layout inside the BIG (Window\..., Data\INI\..., Art\Textures\...). +The updater ships the result as \GeneralsOnlineGameData\GeneralsOnline_Core.big. + +The client mounts it through the generals_online layer of data_packs in settings.json. From the top, the lookup is: loose files in the +game folder, -mod and -modDir, the BIGs in the game folders that are not retail, then the layers user, generals_online and community_patch, +then the retail archives. Every layer has enabled and include_paths, which are mounted in list order (the first one wins). An entry is +game:, user: or an absolute path: a BIG, a folder of BIGs or a *.big pattern. + + "data_packs": { + "user": { "enabled": true, "include_paths": [], "exclude_paths": [] }, + "generals_online": { "enabled": true, "include_paths": [ "user:GeneralsOnlineGameData/GeneralsOnline_Core.big" ] }, + "community_patch": { "enabled": true, "include_paths": [ "user:GeneralsOnlineGameData/500_900_CommunityPatch_CoreINI.big" ] } + } + +user.exclude_paths leaves things out: loose files in the game folder (game:Data/INI/*), the BIGs in the game folders that are not retail +(game:340_ControlBarPro*.big, game:ZH_Generals/*.big) and the include_paths of the user layer itself. * matches any text including folders, +a trailing slash is a folder. Retail archives are never excluded. user.enabled false only turns off user.include_paths. + +The old data_packs.use_community_data_patch switch is read as community_patch.enabled when community_patch is missing. +-disableCommunityDataPatch skips the community_patch layer for one launch. +""" +import os +import struct +import sys + +ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "GeneralsOnline", "GameData") +OUT = sys.argv[1] if len(sys.argv) > 1 else "GeneralsOnline_Core.big" + + +def main(): + files = [] + for dirpath, _, names in os.walk(ROOT): + for name in names: + full = os.path.join(dirpath, name) + rel = os.path.relpath(full, ROOT).replace("/", "\\") + files.append((rel, full)) + files.sort(key=lambda f: f[0].lower()) + if not files: + sys.exit("nothing to pack in " + ROOT) + + names = [rel.encode("ascii") + b"\0" for rel, _ in files] + header = 16 + sum(8 + len(n) for n in names) + table = b"" + body = b"" + offset = header + for (rel, full), n in zip(files, names): + with open(full, "rb") as f: + data = f.read() + table += struct.pack(">II", offset, len(data)) + n + body += data + offset += len(data) + # BIGF: magic, total size (little endian), file count and data start (big endian) + with open(OUT, "wb") as f: + f.write(b"BIGF" + struct.pack("II", len(files), header) + table + body) + print("packed %d files into %s" % (len(files), OUT)) + + +main() diff --git a/vcpkg.json b/vcpkg.json index 6003937b674..04a542a091d 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -2,6 +2,7 @@ "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", "builtin-baseline": "b61d33065508ba0d5e393206abb3cb2366299699", "vcpkg-configuration": { + "overlay-triplets": [ "cmake/triplets" ], "registries": [ { "kind": "git", @@ -19,7 +20,7 @@ { "name": "curl", "default-features": false, - "features": [ "openssl", "websockets" ] + "features": [ "openssl", "websockets", "http2", "http3", "sspi" ] }, { "name": "gamenetworkingsockets",