mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
* Updates the AxisInverted hooks to use the new CC_bind. Currently assumes only Joy0, so this may need further tweaking or replacement * * Add ability to claim up to 4 joysticks for player use, using the launcher (via registry or fs2_open.ini) or the new menu system * Hack into existing control polling system to get the new primary/secondary binding schema to work. This should be reworked into a reverse-lookup system. * Finally break the mouse loose from overlapping with Joystick binidngs * Fix bug where joy button wouldn't register on search function * Fix bug with mousewheel not always decaying as it should. * Fix rebase conflict with selecting item during search mode * Fix bug where mouse buttons were incorrectly labeled. * Move some enums around * Fix bug with joy axis labels being displayed as buttons. * Fix possible bug where Other conflict axis wouldn't clear * Updated default joystick axis text. A new strings.tbl will need to be supplied for localization support * Deprecate nAxisInverted ADE functions and replace with AxisInverted(int, int, bool) * Merge Axis mappings into Control_config * More utility functions for CC_bind and CCB * Adds another ADE function for inverting the axis action, instead of individual bindings * Hacks to get plr file to compile. Currently slices out many of the new features like secondary axis and the mouse axis, and the additional joystick bindings (joy1, joy2, joy3) * Fix bug with uncontrollable spin (slopily) * Fix some warnings * Add flags for Hats and Balls for future implementation * Fix bug where inverting an axis would trigger an assert * Fix bug where joy wasn't detected as present * Most axis actions should be relative, instead of absolute * Hopefully optimize joy_get_unscaled_reading * Properly break out of double-for search function for search and bind modes in config menu * Refactor control_get_axes_readings to provide more consistent behavior * Use JOY_REL_THROTTLE instead of Action::REL_THROTTLE when indexing into Control_config * Bank axis has a default binding, Throttle does not. * Fix bug where conflicts weren't being detected. Fix bug where default presets weren't being detectd * Elegantly break out of double fors with goto. Fix bug with bind mode not detecting axis * Save new binding scheme to playerfile * Add cid_assign() to standardize cid assignment with shorts * Add write and read handlers for int8_t * Remove saving controls to csg. This will be replaced by presets. * Fix bug with reading pilotfile * Use "Joy-n" instead of "joystick n" to match default identifiers in config menu * Fix bug with CC_TYPE_TRIGGER controls always marked as used, if not bound with a key. Fix bug with Joy-0 btn 1 triggering multiple controls that are not bound to it. * Improve readibility of the joy info output to log * Message when a joystick connects and using legacy system. * Fix bug where system joystick 0 was mapped in pJoystick that should've been nullptr * Fix bug where *not* having a joystick will cause bind mode in the config menu to immediately cancel. * Add reverse lookup functions for actions. mActionToVal[] does string -> int, mValToAction() does int -> string * Add the foundation for writing the presets files. * Adds a bunch of reverse lookup functions to get the stringified version of various enums, etc. * presets.h and presets.cpp are in something of a barebones at this point * Add new subdir for preset files 'player/presets' * add utility for finding the distance of a bitfield/bitmask. Used to translate mouse button masks into mouse button id's * Write PresetFileHandler as standalone, instead of a subclass. Still mostly a copy of JSONFileHandler * Make functions to map and reverse-map the bindings for use with the preset files and ultimately the ControlConfigDefaults.tbl as well * Check presets from file to see if they are duplicate with an existing preset. * Check for uniqueness of presets fron controlconfigdefaults.tbl before adding them, and warn the user if there are duplicates. * Rename the test functions into proper load/save. * save function has some standard checks * load function is called on init * make finding duplicates its own function outside of the preset label rendering function * Initial draft of saving presets in-game * Fix bug where all loaded presets were thought to be duplicate of defaults. * Fix bug where preset cycling would get stuck on the first preset other than default * Fix bug where saving a mouse button saves the wrong textified string. * Prevent preset names from being too long. * Crash still occurs if the name is too long, as if set outside of FSO * Workaround text running off the popups in 640x480. Doesn't look as nice, sadly * CSG's now track preset names instead of containing bindings * remake control_config_reset_defaults() into control_config_use_preset() * provide a function to get the currently selected preset. * Do PLR backward compatibility, hopefully. * PLR now tracks presets instead of saving the bindings. * On Loading pre PLR3 or CSG6 data, create a new preset. * Strip off file extension before saving preset files. Jason.json.json just looks dumb * Add "-joy_info" commandline option. Will output SDL joystick info of all detected joysticks to stdout * Fix non-standard behavior with registry_read_string(). * Returning NULL instead of the default value, or an empty string, will trigger an access violation with SCP_string and other string copy functions. * Modify io::joystick::init() to take an optional param, no_register, to use with -joy_info * Address CI complaints * Address CI issues 2. * Supress preset duplicate warning if in unit tests. * Address scripting unittest failure due to incorrect argument type. (Ints don't technically exist in LUA) * Use Mouse enums instead of Joy enums or magic numbers * More signed/unsigned madness * Address CI issues 3 * Address gcc-5 issues. (No auto in lambdas) * Fix bug with bit_distance() * Address gcc-5 issues 2 * Address clang issues * Address clang-tidy issues * Address clang-tidy issues in undosys. May need to be cherry picked to Controls2. * Address clang-tidy issues * Improve logic of preset name prompt. * Fix bug with selected presets not being saved * No, a cstr is not a std::string. * Can't use i there because its already defined. * Fix bug where CSG's wouldn't save selected preset * Fix bug where presets wouldn't be loaded correctly from player files * Fix bug where loaded preset files would not update the preset cycle position correctly * Some QoL changes for the preset name prompt * Address Appveyor issues. * Add forward compatibility for player file. This is required because currently any "missing" data in the pilotfile will trigger an Error, and crash FSO. * Fix bug where mouse X axis was unusable. Apparently stating that MOUSE_Z_AXIS IS MOUSE_Z_AXIS doesn't get the point across. * Address CI issues. * Address CI issues. * sel default assigned to -1 * static_cast enum to an int * Assimilate -joy_info int -get_flags, replacing the existing method that launchers use to get joystick information. * Add '\n' to PST log messages. * Change logic of checking joystick .ini keys: Should the multi-joy key not be found, try legacy joy key. * Add some more missing '\n' to mprintf messages. * Notify the player when converting to the new multi-joy system. (Using ReleaseWarning for now...) * Warn the player if a preset defined in their .PLR could not be found. * Actually disable control items without a valid tab, instead of relying on the fact that they're disabled by default. * Add CC_preset::is_duplicate_of() method to make checking for preset uniqueness less of a brain teaser. * Doxy the first_override param of control_config_common_read_section * Adds the os::dialogs::Information() method, which displays an information-only dialog in similar format to Warning and Error dialogs. Does not throw Warning nor Error exceptions * load_player now uses os::dialogs::Information() instead of ::ReleaseWarning, thus not halting unit tests. * Breaks retail localization support for Joystick and Mice labels - This is because the new multi-joy axis end up very confusing "Joy-0 Joystick/Mouse X Axis" * Renames Joy_axis_text to Axis_text, intended to be used both by joysticks and the mouse. * Moves mouse axis and button labels into an array to ease future XSTR support * Adds asserts to ensure indices are within the array sizes of label arrays * Adds XSTR indices for Axis labels, mouse buttons, and mouse wheels. * Seperates conflict check comparisons from equality comparisons. * Conflict checks can ignore certain combinations of CC_bind::flag's; i.e. an Axis bound to a different control but is inverted or relative will still cause a conflict with another control that has the same axis * Equality comparisons is exact equality, allowing presets to check if binds have the same binding and flag configurations. * More UI string changes and localization * Tweaks positioning of top button labels in CC menu * Adds the following XSTR's: 1659 "Preset: %s" 1660 "Preset: custom" 1661 "Next Preset" * Fix bug where mouse axis turned into mouse button when inverting control by its control name. * Fix bug with the new conflict checking subroutine not working with non-axis controls * Address CI issues. * Don't trigger modal popups while in unit tests. * Address clang tidy issues * use nullptr * be consistent with conditional style * use booleans to stuff debug states, methods with just Assertions in it will trigger a false positive on clang tidy * use string::empty() instead of checking for "". * Remove dead code of -joy_info. Functionality has been merged with -get_flags * Address clang-tidy [readability-simplify-boolean-expr] --z64 says "I don't like it!" * fix axis check * Implement revised axis action virtvars for scripting. Also protect against a control mismatch in the CSG file due to a script-inverted control * Encapsulate CC_bind properly * Add mouse and joystick cid's to AxisInverted description * Update translate_key() and its user to correctly translate multijoy bindings for training messages. * Prompt user trying to escape from config menu when they have changes. * Remove dead function. * Fix crash during training missions due to key translation that would overflow internal buffer. * Went back with cstring as return value instead of SCP string, because caller would need a significant refactor * Update doxy of the textify functions * Unify textify_scancode() and textify_scancode_universal() to ensure common code path. * Add "Multijoy" entry to json caps. (For Knossos) * Make CI happy. * Remove unused lambda captures. * Instead of overwrite during plr2 => plr3 conversion, try to use the existing preset file. * Fix conditional for checking if file exists within presets::save_preset_file() * Revert Majority of PR#3739 * PLR and CSG versions are incremented past versions set by 3739 * Forward compatibility feature of #3739 (Only save sexp containers into the CSG if they are used) is removed * Remove forward compatibilty of PLR file when going in between pre- and post-Controls5 FSO versions * Add missing startSection() and endSection() for csg_write_controls() * Fix clang-tidy error #1 * Fix clang-tidy error #2 * fix clandy tidy error #1 part 2/2 * Fix clang tidy error 3 Co-authored-by: Goober5000 <[email protected]> Co-authored-by: Tyler Blount <[email protected]> Co-authored-by: wookieejedi <[email protected]> Co-authored-by: BMagnu <[email protected]>
970 lines
21 KiB
C++
970 lines
21 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
#include "globalincs/pstypes.h"
|
|
#include "osapi/osregistry.h"
|
|
#include "osapi/osapi.h"
|
|
#include "cmdline/cmdline.h"
|
|
#include "osregistry.h"
|
|
|
|
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
// Stupid Microsoft is not able to fix a simple compile warning: https://connect.microsoft.com/VisualStudio/feedback/details/1342304/level-1-compiler-warnings-in-windows-sdk-shipped-with-visual-studio
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 4091) // ignored on left of '' when no variable is declared
|
|
#pragma warning(pop)
|
|
#include <sddl.h>
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
// ------------------------------------------------------------------------------------------------------------
|
|
// REGISTRY FUNCTIONS
|
|
//
|
|
|
|
char szCompanyName[128] = "Volition";
|
|
char szAppName[128] = "FreeSpace2";
|
|
|
|
int Os_reg_inited = 0;
|
|
|
|
#ifdef WIN32
|
|
// os registry functions -------------------------------------------------------------
|
|
|
|
// This code is needed for compatibility with the old windows registry
|
|
bool userSIDInitialized = false;
|
|
bool userSIDValid = false;
|
|
SCP_string userSID;
|
|
|
|
bool get_user_sid(SCP_string& outStr)
|
|
{
|
|
HANDLE hToken = NULL;
|
|
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken) == FALSE)
|
|
{
|
|
mprintf(("Failed to get process token! Error Code: %d\n", (int)GetLastError()));
|
|
|
|
return false;
|
|
}
|
|
|
|
DWORD dwBufferSize;
|
|
GetTokenInformation(hToken, TokenUser, NULL, 0, &dwBufferSize);
|
|
|
|
PTOKEN_USER ptkUser = (PTOKEN_USER) new byte[dwBufferSize];
|
|
|
|
if (GetTokenInformation(hToken, TokenUser, ptkUser, dwBufferSize, &dwBufferSize))
|
|
{
|
|
CloseHandle(hToken);
|
|
}
|
|
|
|
if (IsValidSid(ptkUser->User.Sid) == FALSE)
|
|
{
|
|
mprintf(("Invalid SID structure detected!\n"));
|
|
|
|
delete[] ptkUser;
|
|
return false;
|
|
}
|
|
|
|
LPTSTR sidName = NULL;
|
|
if (ConvertSidToStringSid(ptkUser->User.Sid, &sidName) == 0)
|
|
{
|
|
mprintf(("Failed to convert SID structure to string! Error Code: %d\n", (int)GetLastError()));
|
|
|
|
delete[] ptkUser;
|
|
return false;
|
|
}
|
|
|
|
outStr.assign(sidName);
|
|
|
|
LocalFree(sidName);
|
|
delete[](byte*) ptkUser;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool needsWOW64()
|
|
{
|
|
#if IS_64BIT
|
|
// 64-bit application always use the Wow6432Node
|
|
return true;
|
|
#else
|
|
BOOL bIsWow64 = FALSE;
|
|
if (!IsWow64Process(GetCurrentProcess(), &bIsWow64))
|
|
{
|
|
mprintf(("Failed to determine if we run under Wow64, registry configuration may fail!\n"));
|
|
return false;
|
|
}
|
|
|
|
return bIsWow64 == TRUE;
|
|
#endif
|
|
}
|
|
|
|
HKEY get_registry_keyname(char* out_keyname, const char* section, bool alternate_path) {
|
|
if (!alternate_path) {
|
|
// Use the original registry path, sometimes breaks for no reason which can be fixed by the code below
|
|
if (section) {
|
|
sprintf(out_keyname, "Software\\%s\\%s\\%s", szCompanyName, szAppName, section);
|
|
}
|
|
else {
|
|
sprintf(out_keyname, "Software\\%s\\%s", szCompanyName, szAppName);
|
|
}
|
|
return HKEY_LOCAL_MACHINE;
|
|
}
|
|
|
|
// Every compiler from Visual Studio 2008 onward should have support for UAC
|
|
if (!userSIDInitialized)
|
|
{
|
|
OSVERSIONINFO versionInfo;
|
|
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
|
|
GetVersionEx(&versionInfo);
|
|
|
|
// Windows Vista is 6.0 which is the first version requiring this
|
|
if (versionInfo.dwMajorVersion >= 6)
|
|
{
|
|
userSIDValid = get_user_sid(userSID);
|
|
}
|
|
userSIDInitialized = true;
|
|
}
|
|
#if !defined(_MSC_VER) || _MSC_VER >= 1400
|
|
if (userSIDValid)
|
|
{
|
|
if (needsWOW64())
|
|
{
|
|
if (section) {
|
|
sprintf(out_keyname, "%s_Classes\\VirtualStore\\MACHINE\\SOFTWARE\\WOW6432Node\\%s\\%s\\%s", userSID.c_str(), szCompanyName, szAppName, section);
|
|
}
|
|
else {
|
|
sprintf(out_keyname, "%s_Classes\\VirtualStore\\MACHINE\\SOFTWARE\\WOW6432Node\\%s\\%s", userSID.c_str(), szCompanyName, szAppName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (section) {
|
|
sprintf(out_keyname, "%s_Classes\\VirtualStore\\MACHINE\\SOFTWARE\\%s\\%s\\%s", userSID.c_str(), szCompanyName, szAppName, section);
|
|
}
|
|
else {
|
|
sprintf(out_keyname, "%s_Classes\\VirtualStore\\MACHINE\\SOFTWARE\\%s\\%s", userSID.c_str(), szCompanyName, szAppName);
|
|
}
|
|
}
|
|
|
|
return HKEY_USERS;
|
|
}
|
|
else
|
|
{
|
|
// This will probably fail
|
|
if (section) {
|
|
sprintf(out_keyname, "Software\\%s\\%s\\%s", szCompanyName, szAppName, section);
|
|
}
|
|
else {
|
|
sprintf(out_keyname, "Software\\%s\\%s", szCompanyName, szAppName);
|
|
}
|
|
|
|
return HKEY_LOCAL_MACHINE;
|
|
}
|
|
#else
|
|
if (section) {
|
|
sprintf(out_keyname, "Software\\%s\\%s\\%s", szCompanyName, szAppName, section);
|
|
}
|
|
else {
|
|
sprintf(out_keyname, "Software\\%s\\%s", szCompanyName, szAppName);
|
|
}
|
|
|
|
return HKEY_LOCAL_MACHINE;
|
|
#endif
|
|
}
|
|
|
|
void registry_write_string(const char *section, const char *name, const char *value)
|
|
{
|
|
HKEY hKey = NULL;
|
|
DWORD dwDisposition;
|
|
char keyname[1024];
|
|
LONG lResult;
|
|
|
|
if (!Os_reg_inited) {
|
|
return;
|
|
}
|
|
|
|
HKEY useHKey = get_registry_keyname(keyname, section, Cmdline_alternate_registry_path);
|
|
|
|
lResult = RegCreateKeyEx(useHKey, // Where to add it
|
|
keyname, // name of key
|
|
0, // DWORD reserved
|
|
NULL, // Object class
|
|
REG_OPTION_NON_VOLATILE, // Save to disk
|
|
KEY_ALL_ACCESS, // Allows all changes
|
|
NULL, // Default security attributes
|
|
&hKey, // Location to store key
|
|
&dwDisposition); // Location to store status of key
|
|
|
|
if (lResult != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (!name) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
lResult = RegSetValueEx(hKey, // Handle to key
|
|
name, // The values name
|
|
0, // DWORD reserved
|
|
REG_SZ, // null terminated string
|
|
(CONST BYTE *)value, // value to set
|
|
(DWORD)(strlen(value) + 1)); // How many bytes to set
|
|
|
|
if (lResult != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
Cleanup:
|
|
if (hKey)
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
void registry_write_uint(const char *section, const char *name, uint value)
|
|
{
|
|
HKEY hKey = NULL;
|
|
DWORD dwDisposition;
|
|
char keyname[1024];
|
|
LONG lResult;
|
|
|
|
if (!Os_reg_inited) {
|
|
return;
|
|
}
|
|
|
|
HKEY useHKey = get_registry_keyname(keyname, section, Cmdline_alternate_registry_path);
|
|
|
|
lResult = RegCreateKeyEx(useHKey, // Where to add it
|
|
keyname, // name of key
|
|
0, // DWORD reserved
|
|
NULL, // Object class
|
|
REG_OPTION_NON_VOLATILE, // Save to disk
|
|
KEY_ALL_ACCESS, // Allows all changes
|
|
NULL, // Default security attributes
|
|
&hKey, // Location to store key
|
|
&dwDisposition); // Location to store status of key
|
|
|
|
if (lResult != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (!name) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
lResult = RegSetValueEx(hKey, // Handle to key
|
|
name, // The values name
|
|
0, // DWORD reserved
|
|
REG_DWORD, // null terminated string
|
|
(CONST BYTE *)&value, // value to set
|
|
4); // How many bytes to set
|
|
|
|
if (lResult != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
if (hKey)
|
|
RegCloseKey(hKey);
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief Reads a string from the INI file or registry.
|
|
*
|
|
* @param[in] section Section to look in
|
|
* @param[in] name Key name of the value
|
|
* @param[in] default_value Value to return, if not found or if OS_reg_inited is false
|
|
*
|
|
* @returns The value of the string with the given name in the given section if found
|
|
*
|
|
* @note Copy the return value somewhere before calling os_read_string again, because it might reuse the same
|
|
* buffer
|
|
*/
|
|
static char registry_tmp_string_data[1024];
|
|
const char * registry_read_string(const char *section, const char *name, const char *default_value)
|
|
{
|
|
HKEY hKey = NULL;
|
|
DWORD dwType, dwLen;
|
|
char keyname[1024];
|
|
LONG lResult;
|
|
|
|
if (!Os_reg_inited) {
|
|
return default_value;
|
|
}
|
|
|
|
HKEY useHKey = get_registry_keyname(keyname, section, Cmdline_alternate_registry_path);
|
|
|
|
lResult = RegOpenKeyEx(useHKey, // Where it is
|
|
keyname, // name of key
|
|
0, // DWORD reserved
|
|
KEY_QUERY_VALUE, // Allows all changes
|
|
&hKey); // Location to store key
|
|
|
|
if (lResult != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (!name) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
dwLen = 1024;
|
|
lResult = RegQueryValueEx(hKey, // Handle to key
|
|
name, // The values name
|
|
NULL, // DWORD reserved
|
|
&dwType, // What kind it is
|
|
(ubyte *)®istry_tmp_string_data, // value to set
|
|
&dwLen); // How many bytes to set
|
|
|
|
if (lResult != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
default_value = registry_tmp_string_data;
|
|
|
|
Cleanup:
|
|
if (hKey)
|
|
RegCloseKey(hKey);
|
|
|
|
return default_value;
|
|
}
|
|
|
|
// Reads a string from the INI file. Default_value must
|
|
// be passed, and if 'name' isn't found, then returns default_value
|
|
uint registry_read_uint(const char *section, const char *name, uint default_value)
|
|
{
|
|
HKEY hKey = NULL;
|
|
DWORD dwType, dwLen;
|
|
char keyname[1024];
|
|
LONG lResult;
|
|
uint tmp_val;
|
|
|
|
if (!Os_reg_inited) {
|
|
return default_value;
|
|
}
|
|
|
|
HKEY useHKey = get_registry_keyname(keyname, section, Cmdline_alternate_registry_path);
|
|
|
|
lResult = RegOpenKeyEx(useHKey, // Where it is
|
|
keyname, // name of key
|
|
0, // DWORD reserved
|
|
KEY_QUERY_VALUE, // Allows all changes
|
|
&hKey); // Location to store key
|
|
|
|
if (lResult != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (!name) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
dwLen = 4;
|
|
lResult = RegQueryValueEx(hKey, // Handle to key
|
|
name, // The values name
|
|
NULL, // DWORD reserved
|
|
&dwType, // What kind it is
|
|
(ubyte *)&tmp_val, // value to set
|
|
&dwLen); // How many bytes to set
|
|
|
|
if (lResult != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
default_value = tmp_val;
|
|
|
|
Cleanup:
|
|
if (hKey)
|
|
RegCloseKey(hKey);
|
|
|
|
return default_value;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef WIN32
|
|
|
|
static time_t filetime_to_timet(const FILETIME& ft)
|
|
{
|
|
ULARGE_INTEGER ull;
|
|
ull.LowPart = ft.dwLowDateTime;
|
|
ull.HighPart = ft.dwHighDateTime;
|
|
return ull.QuadPart / 10000000ULL - 11644473600ULL;
|
|
}
|
|
|
|
static time_t key_mod_time(bool alternate_path) {
|
|
char keyname[1024];
|
|
|
|
HKEY useHKey = get_registry_keyname(keyname, nullptr, alternate_path);
|
|
|
|
HKEY hKey = nullptr;
|
|
auto lResult = RegOpenKeyEx(useHKey, // Where it is
|
|
keyname, // name of key
|
|
0, // DWORD reserved
|
|
KEY_QUERY_VALUE, // Allows all changes
|
|
&hKey); // Location to store key
|
|
|
|
if (lResult != ERROR_SUCCESS) {
|
|
::RegCloseKey(hKey);
|
|
return 0;
|
|
}
|
|
|
|
FILETIME time;
|
|
lResult = RegQueryInfoKey(hKey,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
&time);
|
|
::RegCloseKey(hKey);
|
|
|
|
if (lResult != ERROR_SUCCESS) {
|
|
return 0;
|
|
}
|
|
return filetime_to_timet(time);
|
|
}
|
|
|
|
time_t os_registry_get_last_modification_time() {
|
|
auto standard_time = key_mod_time(false);
|
|
auto alternate_time = key_mod_time(true);
|
|
|
|
return std::max(standard_time, alternate_time);
|
|
}
|
|
|
|
#endif
|
|
|
|
// ------------------------------------------------------------------------------------------------------------
|
|
// REGISTRY DEFINES/VARS
|
|
//
|
|
#ifdef SCP_UNIX
|
|
// Initialize path of old pilot files
|
|
#ifdef __APPLE__
|
|
const char* Osreg_user_dir_legacy = "Library/FS2_Open";
|
|
#else
|
|
const char* Osreg_user_dir_legacy = ".fs2_open";
|
|
#endif // __APPLE__
|
|
#endif
|
|
|
|
const char *Osreg_company_name = "Volition";
|
|
const char *Osreg_class_name = "FreeSpace2Class";
|
|
|
|
const char *Osreg_app_name = "FreeSpace2";
|
|
const char *Osreg_title = "FreeSpace 2";
|
|
|
|
const char *Osreg_config_file_name = "fs2_open.ini";
|
|
|
|
#define DEFAULT_SECTION "Default"
|
|
|
|
typedef struct KeyValue
|
|
{
|
|
char *key;
|
|
char *value;
|
|
|
|
struct KeyValue *next;
|
|
} KeyValue;
|
|
|
|
typedef struct Section
|
|
{
|
|
char *name;
|
|
|
|
struct KeyValue *pairs;
|
|
struct Section *next;
|
|
} Section;
|
|
|
|
typedef struct Profile
|
|
{
|
|
struct Section *sections;
|
|
} Profile;
|
|
|
|
// For string config functions
|
|
static char tmp_string_data[1024];
|
|
|
|
// This code is needed for compatibility with the old windows registry
|
|
|
|
static char *read_line_from_file(FILE *fp)
|
|
{
|
|
char *buf, *buf_start;
|
|
int buflen, eol;
|
|
|
|
buflen = 80;
|
|
buf = (char *)vm_malloc(buflen);
|
|
buf_start = buf;
|
|
eol = 0;
|
|
|
|
do {
|
|
if (buf == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (fgets(buf_start, 80, fp) == NULL) {
|
|
if (buf_start == buf) {
|
|
vm_free(buf);
|
|
return NULL;
|
|
}
|
|
else {
|
|
*buf_start = 0;
|
|
return buf;
|
|
}
|
|
}
|
|
|
|
auto len = strlen(buf_start);
|
|
|
|
if (buf_start[len - 1] == '\n') {
|
|
buf_start[len - 1] = 0;
|
|
eol = 1;
|
|
}
|
|
else {
|
|
buflen += 80;
|
|
|
|
buf = (char *)vm_realloc(buf, buflen);
|
|
|
|
/* be sure to skip over the proper amount of nulls */
|
|
buf_start = buf + (buflen - 80) - (buflen / 80) + 1;
|
|
}
|
|
} while (!eol);
|
|
|
|
return buf;
|
|
}
|
|
|
|
static char *trim_string(char *str)
|
|
{
|
|
char *ptr;
|
|
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
/* kill any comment */
|
|
ptr = strchr(str, ';');
|
|
if (ptr)
|
|
*ptr = 0;
|
|
ptr = strchr(str, '#');
|
|
if (ptr)
|
|
*ptr = 0;
|
|
|
|
ptr = str;
|
|
auto len = strlen(str);
|
|
if (len > 0) {
|
|
ptr += len - 1;
|
|
}
|
|
|
|
while ((ptr > str) && isspace(*ptr)) {
|
|
ptr--;
|
|
}
|
|
|
|
if (*ptr) {
|
|
ptr++;
|
|
*ptr = 0;
|
|
}
|
|
|
|
ptr = str;
|
|
while (*ptr && isspace(*ptr)) {
|
|
ptr++;
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
static Profile *profile_read(const char *file)
|
|
{
|
|
FILE *fp = NULL;
|
|
char *str;
|
|
|
|
if (os_is_legacy_mode()) {
|
|
#ifdef WIN32
|
|
return nullptr; // No config file in legacy mode
|
|
#else
|
|
// Try to use the config file at the old location
|
|
char legacy_path[MAX_PATH_LEN];
|
|
snprintf(legacy_path, MAX_PATH_LEN, "%s/%s/%s", getenv("HOME"), Osreg_user_dir_legacy, file);
|
|
|
|
fp = fopen(legacy_path, "rt");
|
|
#endif
|
|
}
|
|
else {
|
|
fp = fopen(os_get_config_path(file).c_str(), "rt");
|
|
}
|
|
|
|
if (fp == NULL)
|
|
return NULL;
|
|
|
|
Profile *profile = (Profile *)vm_malloc(sizeof(Profile));
|
|
profile->sections = NULL;
|
|
|
|
Section **sp_ptr = &(profile->sections);
|
|
Section *sp = NULL;
|
|
|
|
KeyValue **kvp_ptr = NULL;
|
|
|
|
while ((str = read_line_from_file(fp)) != NULL) {
|
|
char *ptr = trim_string(str);
|
|
|
|
if (*ptr == '[') {
|
|
ptr++;
|
|
|
|
char *pend = strchr(ptr, ']');
|
|
if (pend != NULL) {
|
|
// if (pend[1]) { /* trailing garbage! */ }
|
|
|
|
*pend = 0;
|
|
|
|
if (*ptr) {
|
|
sp = (Section *)vm_malloc(sizeof(Section));
|
|
sp->next = NULL;
|
|
|
|
sp->name = vm_strdup(ptr);
|
|
sp->pairs = NULL;
|
|
|
|
*sp_ptr = sp;
|
|
sp_ptr = &(sp->next);
|
|
|
|
kvp_ptr = &(sp->pairs);
|
|
} // else { /* null name! */ }
|
|
} // else { /* incomplete section name! */ }
|
|
}
|
|
else {
|
|
if (*ptr) {
|
|
char *key = ptr;
|
|
char *value = NULL;
|
|
|
|
ptr = strchr(ptr, '=');
|
|
if (ptr != NULL) {
|
|
*ptr = 0;
|
|
ptr++;
|
|
|
|
value = ptr;
|
|
} // else { /* random garbage! */ }
|
|
|
|
if (key && *key && value /* && *value */) {
|
|
if (sp != NULL) {
|
|
KeyValue *kvp = (KeyValue *)vm_malloc(sizeof(KeyValue));
|
|
|
|
kvp->key = vm_strdup(key);
|
|
kvp->value = vm_strdup(value);
|
|
|
|
kvp->next = NULL;
|
|
|
|
*kvp_ptr = kvp;
|
|
kvp_ptr = &(kvp->next);
|
|
} // else { /* key/value with no section! */
|
|
} // else { /* malformed key/value entry! */ }
|
|
} // else it's just a comment or empty string
|
|
}
|
|
|
|
vm_free(str);
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
return profile;
|
|
}
|
|
|
|
static void profile_free(Profile *profile)
|
|
{
|
|
if (profile == NULL)
|
|
return;
|
|
|
|
Section *sp = profile->sections;
|
|
while (sp != NULL) {
|
|
Section *st = sp;
|
|
KeyValue *kvp = sp->pairs;
|
|
|
|
while (kvp != NULL) {
|
|
KeyValue *kvt = kvp;
|
|
|
|
vm_free(kvp->key);
|
|
vm_free(kvp->value);
|
|
|
|
kvp = kvp->next;
|
|
vm_free(kvt);
|
|
}
|
|
|
|
vm_free(sp->name);
|
|
|
|
sp = sp->next;
|
|
vm_free(st);
|
|
}
|
|
|
|
vm_free(profile);
|
|
}
|
|
|
|
static Profile *profile_update(Profile *profile, const char *section, const char *key, const char *value)
|
|
{
|
|
if (profile == NULL) {
|
|
profile = (Profile *)vm_malloc(sizeof(Profile));
|
|
|
|
profile->sections = NULL;
|
|
}
|
|
|
|
KeyValue *kvp;
|
|
|
|
Section **sp_ptr = &(profile->sections);
|
|
Section *sp = profile->sections;
|
|
|
|
while (sp != NULL) {
|
|
if (strcmp(section, sp->name) == 0) {
|
|
KeyValue **kvp_ptr = &(sp->pairs);
|
|
kvp = sp->pairs;
|
|
|
|
while (kvp != NULL) {
|
|
if (strcmp(key, kvp->key) == 0) {
|
|
vm_free(kvp->value);
|
|
|
|
if (value == NULL) {
|
|
*kvp_ptr = kvp->next;
|
|
|
|
vm_free(kvp->key);
|
|
vm_free(kvp);
|
|
}
|
|
else {
|
|
kvp->value = vm_strdup(value);
|
|
}
|
|
|
|
/* all done */
|
|
return profile;
|
|
}
|
|
|
|
kvp_ptr = &(kvp->next);
|
|
kvp = kvp->next;
|
|
}
|
|
|
|
if (value != NULL) {
|
|
/* key not found */
|
|
kvp = (KeyValue *)vm_malloc(sizeof(KeyValue));
|
|
kvp->next = NULL;
|
|
kvp->key = vm_strdup(key);
|
|
kvp->value = vm_strdup(value);
|
|
}
|
|
|
|
*kvp_ptr = kvp;
|
|
|
|
/* all done */
|
|
return profile;
|
|
}
|
|
|
|
sp_ptr = &(sp->next);
|
|
sp = sp->next;
|
|
}
|
|
|
|
/* section not found */
|
|
sp = (Section *)vm_malloc(sizeof(Section));
|
|
sp->next = NULL;
|
|
sp->name = vm_strdup(section);
|
|
|
|
kvp = (KeyValue *)vm_malloc(sizeof(KeyValue));
|
|
kvp->next = NULL;
|
|
kvp->key = vm_strdup(key);
|
|
kvp->value = vm_strdup(value);
|
|
|
|
sp->pairs = kvp;
|
|
|
|
*sp_ptr = sp;
|
|
|
|
return profile;
|
|
}
|
|
|
|
static char *profile_get_value(Profile *profile, const char *section, const char *key)
|
|
{
|
|
if (profile == NULL)
|
|
return NULL;
|
|
|
|
Section *sp = profile->sections;
|
|
|
|
while (sp != NULL) {
|
|
if (stricmp(section, sp->name) == 0) {
|
|
KeyValue *kvp = sp->pairs;
|
|
|
|
while (kvp != NULL) {
|
|
if (strcmp(key, kvp->key) == 0) {
|
|
return kvp->value;
|
|
}
|
|
kvp = kvp->next;
|
|
}
|
|
}
|
|
|
|
sp = sp->next;
|
|
}
|
|
|
|
/* not found */
|
|
return NULL;
|
|
}
|
|
|
|
static void profile_save(Profile *profile, const char *file)
|
|
{
|
|
FILE *fp = NULL;
|
|
char tmp[MAX_PATH] = "";
|
|
char tmp2[MAX_PATH] = "";
|
|
|
|
if (profile == NULL)
|
|
return;
|
|
|
|
fp = fopen(os_get_config_path(file).c_str(), "wt");
|
|
|
|
if (fp == NULL)
|
|
return;
|
|
|
|
Section *sp = profile->sections;
|
|
|
|
while (sp != NULL) {
|
|
sprintf(tmp, NOX("[%s]\n"), sp->name);
|
|
fputs(tmp, fp);
|
|
|
|
KeyValue *kvp = sp->pairs;
|
|
while (kvp != NULL) {
|
|
sprintf(tmp2, NOX("%s=%s\n"), kvp->key, kvp->value);
|
|
fputs(tmp2, fp);
|
|
kvp = kvp->next;
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
|
|
sp = sp->next;
|
|
}
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
// os registry functions -------------------------------------------------------------
|
|
|
|
static Profile* Osreg_profile = nullptr;
|
|
|
|
// initialize the registry. setup default keys to use
|
|
void os_init_registry_stuff(const char *company, const char *app)
|
|
{
|
|
if (company) {
|
|
strcpy_s(szCompanyName, company);
|
|
}
|
|
else {
|
|
strcpy_s(szCompanyName, Osreg_company_name);
|
|
}
|
|
|
|
if (app) {
|
|
strcpy_s(szAppName, app);
|
|
}
|
|
else {
|
|
strcpy_s(szAppName, Osreg_app_name);
|
|
}
|
|
|
|
Osreg_profile = profile_read(Osreg_config_file_name);
|
|
|
|
Os_reg_inited = 1;
|
|
}
|
|
void os_deinit_registry_stuff()
|
|
{
|
|
if (Osreg_profile != nullptr) {
|
|
profile_free(Osreg_profile);
|
|
Osreg_profile = nullptr;
|
|
}
|
|
}
|
|
bool os_config_has_value(const char* section, const char* name) {
|
|
#ifdef WIN32
|
|
if (Osreg_profile == nullptr) {
|
|
// No config file, fall back to registy
|
|
return registry_read_string(section, name, nullptr) != nullptr;
|
|
}
|
|
#endif
|
|
if (section == nullptr)
|
|
section = DEFAULT_SECTION;
|
|
|
|
char *ptr = profile_get_value(Osreg_profile, section, name);
|
|
|
|
return ptr != nullptr;
|
|
}
|
|
|
|
const char *os_config_read_string(const char *section, const char *name, const char *default_value)
|
|
{
|
|
#ifdef WIN32
|
|
if (Osreg_profile == nullptr) {
|
|
// No config file, fall back to registy
|
|
return registry_read_string(section, name, default_value);
|
|
}
|
|
#endif
|
|
nprintf(("Registry", "os_config_read_string(): section = \"%s\", name = \"%s\", default value: \"%s\"\n",
|
|
(section) ? section : DEFAULT_SECTION, name, (default_value) ? default_value : NOX("NULL")));
|
|
|
|
if (section == NULL)
|
|
section = DEFAULT_SECTION;
|
|
|
|
char *ptr = profile_get_value(Osreg_profile, section, name);
|
|
|
|
if (ptr != NULL) {
|
|
strncpy(tmp_string_data, ptr, 1023);
|
|
default_value = tmp_string_data;
|
|
}
|
|
|
|
return default_value;
|
|
}
|
|
|
|
unsigned int os_config_read_uint(const char *section, const char *name, unsigned int default_value)
|
|
{
|
|
#ifdef WIN32
|
|
if (Osreg_profile == nullptr) {
|
|
// No config file, fall back to registy
|
|
return registry_read_uint(section, name, default_value);
|
|
}
|
|
#endif
|
|
|
|
if (section == NULL)
|
|
section = DEFAULT_SECTION;
|
|
|
|
char *ptr = profile_get_value(Osreg_profile, section, name);
|
|
|
|
if (ptr != NULL) {
|
|
default_value = atoi(ptr);
|
|
}
|
|
|
|
return default_value;
|
|
}
|
|
|
|
void os_config_write_string(const char *section, const char *name, const char *value)
|
|
{
|
|
#ifdef WIN32
|
|
// When there is no config file then it shouldn't be created because that would "hide" all previous settings
|
|
// Instead fall back to writing the settings to the config file
|
|
if (Osreg_profile == nullptr) {
|
|
registry_write_string(section, name, value);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (section == NULL)
|
|
section = DEFAULT_SECTION;
|
|
|
|
Osreg_profile = profile_update(Osreg_profile, section, name, value);
|
|
profile_save(Osreg_profile, Osreg_config_file_name);
|
|
}
|
|
|
|
void os_config_write_uint(const char *section, const char *name, unsigned int value)
|
|
{
|
|
#ifdef WIN32
|
|
// When there is no config file then it shouldn't be created because that would "hide" all previous settings
|
|
// Instead fall back to writing the settings to the config file
|
|
if (Osreg_profile == nullptr) {
|
|
registry_write_uint(section, name, value);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (section == NULL)
|
|
section = DEFAULT_SECTION;
|
|
|
|
char buf[21];
|
|
|
|
snprintf(buf, 20, "%u", value);
|
|
|
|
Osreg_profile = profile_update(Osreg_profile, section, name, buf);
|
|
profile_save(Osreg_profile, Osreg_config_file_name);
|
|
}
|
|
|