#include "utils.h" #include "localization.h" Json Utils::read_json(std::wstring file, int maxRetries, int retryDelay) { int attempts = 0; while (attempts < maxRetries) { std::ifstream in(file); if (!in.is_open()) { if (++attempts == maxRetries) { return Json(); } Sleep(retryDelay); continue; } std::stringstream jsonbuffer; jsonbuffer << in.rdbuf(); in.close(); std::string jsonstr = jsonbuffer.str(); std::string errstr; Json json = Json::parse(jsonstr, errstr); if (!errstr.empty()) { return Json(); } return json; } return Json(); } bool Utils::write_json(std::wstring file, const Json& json) { std::ofstream out(file); if (!out.is_open()) { return false; } out << json.dump(); out.close(); return true; } bool Utils::is_valid_json(std::wstring file, int maxRetries, int retryDelay) { int attempts = 0; while (attempts < maxRetries) { std::ifstream in(file); if (!in.is_open()) { if (++attempts == maxRetries) { return false; } Sleep(retryDelay); continue; } std::stringstream jsonbuffer; jsonbuffer << in.rdbuf(); in.close(); std::string jsonstr = jsonbuffer.str(); std::string errstr; Json json = Json::parse(jsonstr, errstr); return errstr.empty(); } return false; } // Serialize json values to raw array of bytes Json::object serialize_json(const Json::object& input_json, std::vector ignoredTags) { Json::object output_json; for (auto& kv : input_json) { const std::string& key = kv.first; const Json& val = kv.second; // Any tags to ignore that are intended to be readable if (std::find(ignoredTags.begin(), ignoredTags.end(), key) != ignoredTags.end()) { output_json.emplace(key, val); continue; } const std::string& str = val.string_value(); std::vector bytes; for (unsigned char c : str) { bytes.push_back(static_cast(c)); } output_json.emplace(key, bytes); } return output_json; } Json::object deserialize_json(const Json::object& input_json) { Json::object output_json; for (auto& kv : input_json) { const std::string& key = kv.first; const Json& val = kv.second; // Any values that are not bytes // This requires a json that only uses nested arrays that contain raw bytes if (!val.is_array()) { output_json.emplace(key, val); continue; } std::string str; for (const Json& byte_json : val.array_items()) { // Convert each Json value back to string character str.push_back(static_cast(byte_json.int_value())); } output_json.emplace(key, str); } return output_json; } std::string Utils::string_wide_to_narrow(std::wstring wstr) { size_t len = wcstombs(nullptr, wstr.c_str(), 0) + 1; char* buffer = new char[len]; wcstombs(buffer, wstr.c_str(), len); std::string str(buffer); // Cleaning up delete[] buffer; return str; } // Using an child process to get output from a command -- primarily any output from an outside executable // https://stackoverflow.com/a/35658917 std::wstring Utils::run_command(const std::wstring& command, std::string& streamedStr, PROCESS_INFORMATION& processHandle) { HANDLE hPipeRead, hPipeWrite; SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES) }; saAttr.bInheritHandle = TRUE; // Pipe handles are inherited by child process. saAttr.lpSecurityDescriptor = NULL; // Create a pipe to get results from child's stdout. if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0)) { return L""; } STARTUPINFOW si = { sizeof(STARTUPINFOW) }; si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; si.hStdOutput = hPipeWrite; si.hStdError = hPipeWrite; si.wShowWindow = SW_HIDE; BOOL fSuccess = CreateProcessW(NULL, (LPWSTR)command.c_str(), NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &processHandle); if (!fSuccess) { CloseHandle(hPipeWrite); CloseHandle(hPipeRead); return L""; } bool bProcessEnded = false; for (; !bProcessEnded;) { // Give some timeslice (50 ms), so we won't waste 100% CPU. bProcessEnded = WaitForSingleObject(processHandle.hProcess, 50) == WAIT_OBJECT_0; // Even if process exited - we continue reading, if // there is some data available over pipe. for (;;) { char buf[1024]; DWORD dwRead = 0; DWORD dwAvail = 0; if (!::PeekNamedPipe(hPipeRead, NULL, 0, NULL, &dwAvail, NULL)) break; if (!dwAvail) // No data available, return break; if (!::ReadFile(hPipeRead, buf, min(sizeof(buf) - 1, dwAvail), &dwRead, NULL) || !dwRead) // Error, the child process might ended break; buf[dwRead] = 0; streamedStr += buf; } } CloseHandle(hPipeWrite); CloseHandle(hPipeRead); if (processHandle.hProcess != NULL && processHandle.hThread != NULL && processHandle.hProcess != INVALID_HANDLE_VALUE && processHandle.hThread != INVALID_HANDLE_VALUE) { CloseHandle(processHandle.hProcess); CloseHandle(processHandle.hThread); } // Convert the result to std::wstring std::wstring wstrResult(streamedStr.begin(), streamedStr.end()); return wstrResult; } bool Utils::write_to_stdin(const std::string& serial_select_command, const std::wstring& exe_path, const std::string& command, std::vector* serials) // Optionally return serials from responses. { // Create pipe for stdin of child process. HANDLE stdin_read; HANDLE stdin_write; SECURITY_ATTRIBUTES security_att; security_att.nLength = sizeof(SECURITY_ATTRIBUTES); security_att.bInheritHandle = TRUE; security_att.lpSecurityDescriptor = NULL; if (!CreatePipe(&stdin_read, &stdin_write, &security_att, 0) || !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0)) { return false; } // Create pipe for stdout of child process. HANDLE stdout_read = NULL; HANDLE stdout_write = NULL; if (!CreatePipe(&stdout_read, &stdout_write, &security_att, 0) || !SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0)) { return false; } // Open up process and run command. STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); si.hStdInput = stdin_read; si.hStdOutput = stdout_write; si.dwFlags |= STARTF_USESTDHANDLES; ZeroMemory(&pi, sizeof(pi)); // Create lighthouse child process. if (!CreateProcess(exe_path.c_str(), NULL, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { CloseHandle(stdout_write); CloseHandle(stdout_read); CloseHandle(stdin_write); CloseHandle(stdin_read); return false; } // If we specified a serial, make sure we switch to that. DWORD written_bytes = 0; if (serial_select_command.length() > 0) { if (!WriteFile(stdin_write, serial_select_command.c_str(), (DWORD)serial_select_command.size(), &written_bytes, NULL)) { CloseHandle(stdout_write); CloseHandle(stdout_read); CloseHandle(stdin_write); CloseHandle(stdin_read); TerminateProcess(pi.hProcess, 0); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return false; } } // Write to stdin to receive config file. if (!WriteFile(stdin_write, command.c_str(), (DWORD)command.size(), &written_bytes, NULL)) { CloseHandle(stdout_write); CloseHandle(stdout_read); CloseHandle(stdin_write); CloseHandle(stdin_read); TerminateProcess(pi.hProcess, 0); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return false; } // Clear out existing stdin buffer. CHAR stdout_buf[4096]; DWORD read_bytes = 0; if (!ReadFile(stdout_read, stdout_buf, 4096, &read_bytes, NULL)) { CloseHandle(stdout_write); CloseHandle(stdout_read); CloseHandle(stdin_write); CloseHandle(stdin_read); TerminateProcess(pi.hProcess, 0); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return false; } // Read all of the serials of USB attached, so we query the HMD only. if (serials != NULL) { // Request all USB attached serials. std::string serial_command = "serial\r\n"; if (!WriteFile(stdin_write, serial_command.c_str(), (DWORD)serial_command.size(), &written_bytes, NULL)) { CloseHandle(stdout_write); CloseHandle(stdout_read); CloseHandle(stdin_write); CloseHandle(stdin_read); TerminateProcess(pi.hProcess, 0); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return false; } if (!ReadFile(stdout_read, stdout_buf, 4096, &read_bytes, NULL)) { CloseHandle(stdout_write); CloseHandle(stdout_read); CloseHandle(stdin_write); CloseHandle(stdin_read); TerminateProcess(pi.hProcess, 0); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return false; } if (read_bytes < 4096) { stdout_buf[read_bytes] = '\0'; } std::string response = stdout_buf; size_t pos = response.find("LHR", 0); while (pos != std::string::npos) { serials->push_back(response.substr(pos, 12)); pos = response.find("LHR", pos + 1); } // Remove duplicates of the serial found in the stdout output. std::sort(serials->begin(), serials->end()); serials->erase(std::unique(serials->begin(), serials->end()), serials->end()); } // Wait to exit. std::string exit = "exit\r\n"; if (!WriteFile(stdin_write, exit.c_str(), (DWORD)exit.size(), &written_bytes, NULL)) { CloseHandle(stdout_write); CloseHandle(stdout_read); CloseHandle(stdin_write); CloseHandle(stdin_read); TerminateProcess(pi.hProcess, 0); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return false; } DWORD exit_code; while (GetExitCodeProcess(pi.hProcess, &exit_code) && exit_code == STILL_ACTIVE) { Sleep(1); // Don't clog the core. } CloseHandle(stdout_write); CloseHandle(stdout_read); CloseHandle(stdin_write); CloseHandle(stdin_read); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return true; } // Performs a full read, modify, write of the configuration memory region on the // ATSAMG55 microcontroller. On the first time valid config memory is read, the // values are saved to a json file on disk. bool Utils::write_mem_config(HidHandler& hid, const std::wstring& config_mem_json_file, std::vector new_values) { ui.fetching_config.store(true); int reread_count = 0; bool config_verified = false; int hid_read_timeout = 1000; // milliseconds // Try to get a valid config 3 times. If it is not valid, fail out // of this function. while ((!config_verified) && (reread_count < 3)) { // Read current config for (uint32_t b = 0; b < 16; ++b) { int hid_timeout_counter = 0; hid.command_read_config(b); while (hid.waiting_on_block.load() != 0xFF) { Sleep(1); if (hid_timeout_counter >= hid_read_timeout) break; hid_timeout_counter++; } } if (hid.verify_config()) { // Now we know the memory data is valid. Let's check the // serial number and ensure it is a proper value hid.parse_config(); // reads values from raw bytes and places them in vector "hid.config" config_verified = true; // Verify if the Flash page follows the format rules. Not currently doing sanity checks on values. } reread_count++; } if (!config_verified) { ui.fetching_config.store(false); return false; } if (!config_mem_json_file.empty()) { // If we got here, then we know the config is fully valid // No backup file? Save it now. //// TODO: Load the saved config from file when failed to load std::wstring headset_serial(hid_handler.hmd_serial.begin(), hid_handler.hmd_serial.end()); std::wstring backup_file_name_w = config_mem_json_file + L"_" + headset_serial + L".json"; if (!std::filesystem::exists(backup_file_name_w.c_str())) { // Let's save the current values to a JSON file Json::object jsonvals; for (auto& val : hid.config) { // create a json entry with the string version of the tag number jsonvals.emplace(std::to_string(std::get<0>(val)), std::get<1>(val)); } Json config_mem = serialize_json(jsonvals, Config_Tags::readableTags); std::ofstream config_out(backup_file_name_w.c_str()); if (config_out.is_open()) { config_out << config_mem.dump(); config_out.flush(); config_out.close(); } } } // Safe to make changes! hid.save_config(new_values); ui.fetching_config.store(false); return true; } // Override to perform the RGB, fan, and brightness updates only. // This is called during the "Save" button event bool Utils::update_config(HidHandler& hid, UI_Run_State& rs) { unsigned short converted_brightness; if (rs.overdrive) { converted_brightness = (unsigned short)((((float)(rs.overdrive_brightness - 100) * 0.01f) * 553) + 266); } else { converted_brightness = (unsigned short)((((float)rs.brightness * 0.01f) * 216) + 50); } // Create a vector and send it to the default update_config function std::vector new_entries; std::string rgb_val; rgb_val += (unsigned char)(rs.color[0] * 255.0f); rgb_val += (unsigned char)(rs.color[1] * 255.0f); rgb_val += (unsigned char)(rs.color[2] * 255.0f); std::string fan_val; fan_val += rs.fan_speed; std::string bright_val; bright_val += (converted_brightness & 0x00FF); bright_val += ((converted_brightness & 0xFF00) >> 8); std::string prox_val; prox_val += (rs.prox_offset & 0x00FF); prox_val += ((rs.prox_offset & 0xFF00) >> 8); std::string vxr_val; vxr_val += (rs.vxr_auto_off ? 0x1 : 0); new_entries.push_back(std::make_tuple(Config_Tags::RGB_Color, rgb_val)); new_entries.push_back(std::make_tuple(Config_Tags::Fan_Speed, fan_val)); new_entries.push_back(std::make_tuple(Config_Tags::Display_Brightness, bright_val)); new_entries.push_back(std::make_tuple(Config_Tags::Prox_Offset, prox_val)); new_entries.push_back(std::make_tuple(Config_Tags::VXR_Sleep_Enable, vxr_val)); return write_mem_config(hid, rs.config_memory_backup_path, new_entries); } // Writes values from a memory configuration backup file bool Utils::restore_mem_config(HidHandler& hid, const std::wstring& config_mem_json_file) { Json backup_json = utils.read_json(config_mem_json_file); if (!backup_json.is_null()) { Json deserialized_json = deserialize_json(backup_json.object_items()); // Convert values from backup json to the tuple std::vector new_values; new_values.reserve(deserialized_json.object_items().size()); for (auto& key_v : deserialized_json.object_items()) { unsigned char id = static_cast(std::stoi(key_v.first)); std::string val = key_v.second.string_value(); new_values.emplace_back(id, val); } return write_mem_config(hid, L"", new_values); } return false; } void Utils::mic_volume_fix(UI_Run_State& rs) { // Find the Beyond microphone device via EnumAudioEndpoints IMMDeviceEnumerator* deviceEnumerator = NULL; HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID*)&deviceEnumerator); IMMDeviceCollection* deviceCollection = NULL; hr = deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection); if (FAILED(hr)) { std::cerr << "Failed to enumerate audio endpoints." << std::endl; deviceEnumerator->Release(); return; } UINT deviceCount; deviceCollection->GetCount(&deviceCount); IMMDevice* targetDevice = NULL; for (UINT i = 0; i < deviceCount; ++i) { IMMDevice* device = NULL; hr = deviceCollection->Item(i, &device); if (SUCCEEDED(hr)) { IPropertyStore* propertyStore; hr = device->OpenPropertyStore(STGM_READ, &propertyStore); if (SUCCEEDED(hr)) { PROPVARIANT friendlyName; PropVariantInit(&friendlyName); hr = propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName); if (SUCCEEDED(hr)) { if (wcsstr(friendlyName.pwszVal, L"Beyond") != NULL) { targetDevice = device; targetDevice->AddRef(); // Increase reference count since we'll be using it outside the loop PropVariantClear(&friendlyName); propertyStore->Release(); break; } PropVariantClear(&friendlyName); } propertyStore->Release(); } device->Release(); } } if (targetDevice == NULL) { // Beyond is attached, but we failed to find microphone; // this will only happen in much older firmwares, e.g. <= 0.2.4 // in this case, skip setting the volume deviceEnumerator->Release(); deviceCollection->Release(); return; } deviceCollection->Release(); deviceEnumerator->Release(); IAudioEndpointVolume* endpointVolume = NULL; hr = targetDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&endpointVolume); targetDevice->Release(); auto fw_info = split_headset_firmware(); // Set mic volume to 54% for older firmwares, else 90% if (std::get<0>(fw_info) == 0 && std::get<1>(fw_info) == 2 && std::get<2>(fw_info) <= 12) { endpointVolume->SetMasterVolumeLevelScalar(0.54f, NULL); } else { endpointVolume->SetMasterVolumeLevelScalar(0.9f, NULL); } } bool Utils::install_et_driver(UI_Run_State& rs) { if (rs.valid_path) { std::filesystem::path driverFolder = std::filesystem::absolute(std::filesystem::path("eyetracking\\ETDriver")); std::wstring vrpathreg = rs.steamvr_path + L"\\bin\\win64\\vrpathreg.exe"; bool tool_valid = std::filesystem::exists(vrpathreg) && std::filesystem::is_regular_file(vrpathreg); bool driver_valid = std::filesystem::exists(driverFolder) && std::filesystem::is_directory(driverFolder); if (tool_valid && driver_valid) { // Issue the command to add the external driver path with vrpathreg PROCESS_INFORMATION processHandle; std::string cmdStr; std::wstring result = run_command(vrpathreg + L" adddriver \"" + driverFolder.c_str() + L"\"", cmdStr, processHandle); // vrpathreg returned output if (result.empty()) { update_et_driver_status(rs); return true; } } else { MessageBox(glfwGetWin32Window(ui.window), !tool_valid ? LWSTR(StringID::ERROR_ET_INSTALL_FAIL_TOOL).c_str() : LWSTR(StringID::ERROR_ET_INSTALL_FAIL_DRIVER_FOLDER).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } } else { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_ET_INSTALL_FAIL_STEAMVR_FOLDER).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } return false; } void Utils::update_et_driver_status(UI_Run_State& rs) { if (rs.valid_path) { std::wstring vrpathreg = rs.steamvr_path + L"\\bin\\win64\\vrpathreg.exe"; bool file_valid = std::filesystem::exists(vrpathreg) && std::filesystem::is_regular_file(vrpathreg); if (file_valid) { PROCESS_INFORMATION processHandle; std::string cmdStr; std::wstring result = run_command(vrpathreg + L" show", cmdStr, processHandle); // Check for keywords to validate the driver path exists if (!result.empty() && result.find(L"ETDriver") != std::wstring::npos && result.find(L"BeyondEyetracking") != std::wstring::npos) { rs.installed_et_driver = true; } else { rs.installed_et_driver = false; } } } } HANDLE Utils::get_et_handle() { HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, L"com_bigscreen_et_process"); return hMutex; } bool Utils::launch_et_client() { std::filesystem::path clientPath = std::filesystem::absolute(std::filesystem::path("eyetracking\\ETClient\\BeyondET.exe")); bool file_valid = std::filesystem::exists(clientPath) && std::filesystem::is_regular_file(clientPath); if (file_valid) { std::wstring workingDir = clientPath.parent_path().wstring(); HANDLE existing_handle = get_et_handle(); if (existing_handle) { // Already running CloseHandle(existing_handle); MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_ET_ALREADY_RUNNING).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); return 0; } // Issue the command to add the external driver path with vrpathreg STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; CreateProcess(clientPath.c_str(), NULL, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, workingDir.c_str(), &si, &pi); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_ET_LAUNCH).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } return false; } bool Utils::write_steamvr_settings(UI_Run_State& rs, SteamVR_Flagged_Setting setting_details) { if (!rs.steamvr_settings_file.empty()) { std::wstring steamvr_settings = rs.steamvr_settings_file; bool file_valid = std::filesystem::exists(steamvr_settings.c_str()) && std::filesystem::is_regular_file(steamvr_settings.c_str()); if (file_valid) { Json steamvr_json = read_json(steamvr_settings, 1); if (!steamvr_json.is_null()) { std::string errstr; Json target_Value = Json::parse(setting_details.steamvr_flag_value, errstr); if (errstr.empty()) { // Get mutable objects Json::object new_settings = steamvr_json.object_items(); Json::object category_obj; auto category_it = new_settings.find(setting_details.json_category); // Use any existing category if (category_it != new_settings.end() && category_it->second.is_object()) { category_obj = category_it->second.object_items(); } // Create an empty category if it does not exist else { category_obj = Json::object(); } // Modify the value inside the category object if (target_Value.is_bool()) { category_obj[setting_details.steamvr_setting] = !target_Value.bool_value(); // Write the opposite of the target boolean value } else if (target_Value.is_number()) { category_obj[setting_details.steamvr_setting] = target_Value.number_value(); } else if (target_Value.is_string()) { category_obj[setting_details.steamvr_setting] = target_Value.string_value(); } // Reassign the modified category back to the main settings object new_settings[setting_details.json_category] = category_obj; return write_json(rs.steamvr_settings_file, (Json)new_settings); } } MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_READING_STEAMVR_SETTINGS).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); return false; } } MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_FINDING_STEAMVR_SETTINGS).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); return false; } std::wstring Utils::get_steam_config_folder(UI_Run_State& rs) { // Use saved path if (!rs.steam_config_path.empty()) { return rs.steam_config_path; } // Get the steamvr location from vrpathreg if (rs.valid_path) { std::wstring vrpathreg = rs.steamvr_path + L"\\bin\\win64\\vrpathreg.exe"; bool file_valid = std::filesystem::exists(vrpathreg.c_str()) && std::filesystem::is_regular_file(vrpathreg.c_str()); if (file_valid) { // This grabs the location from SteamVR registered paths PROCESS_INFORMATION processHandle; std::string cmdStr; std::wstring paths = utils.run_command(vrpathreg + L" show", cmdStr, processHandle); if (!paths.empty()) { // Get only the config path std::wstring prefix = L"Config path = "; size_t pos = paths.find(prefix); if (pos != std::wstring::npos) { size_t start = pos + prefix.length(); size_t end = paths.find(L'\n', start); std::wstring result = paths.substr(start, (end == std::wstring::npos) ? end : end - start); // Remove any trailing '\r' if (!result.empty() && result.back() == L'\r') { result.pop_back(); } // Save the result so that we don't need to use vrpathreg all the time rs.steam_config_path = result; utils.write_settings_file(rs); return result; } } } } return std::wstring(); } void Utils::set_steamvr_color_gain(UI_Run_State& rs, std::wstring prop, char* next) { std::thread gain_set([&](std::wstring prop, char* next) { std::wstring gain; // Parse to wide string from double try { std::string parsed(next); parsed = std::to_string(std::stod(parsed)); gain = std::wstring(parsed.begin(), parsed.end()); } catch (...) { return; } if (rs.valid_path) { std::wstring vrcmd = rs.steamvr_path + L"\\bin\\win64\\vrcmd.exe"; bool file_valid = std::filesystem::exists(vrcmd.c_str()) && std::filesystem::is_regular_file(vrcmd.c_str()); if (file_valid) { // Pass the settings change through vrcmd.exe PROCESS_INFORMATION processHandle; std::string out; std::wstring cmd = vrcmd + L" --set-settings-float " + L"\"" + prop + L"\" " + L"\"" + gain + L"\""; utils.run_command(cmd, out, processHandle); if (out.find("VRInitError") != std::string::npos) { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_COLOR_TINT_RUNNING).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } } else { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_COLOR_TINT_TOOL).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } } }, prop, next); gain_set.detach(); } Json Utils::get_steamvr_settings(UI_Run_State& rs) { std::wstring streamvr_config = get_steam_config_folder(rs); if (streamvr_config.empty()) { return Json(); } std::wstring steamvr_settings = streamvr_config + L"\\steamvr.vrsettings"; bool file_valid = std::filesystem::exists(steamvr_settings.c_str()) && std::filesystem::is_regular_file(steamvr_settings.c_str()); if (file_valid) { return utils.read_json(steamvr_settings); } return Json(); } void Utils::find_flagged_settings(UI_Run_State& rs) { if (steamvr_check.joinable()) { steamvr_check.join(); } steamvr_check = std::thread([&]() { rs.steamvr_settings_file.clear(); if (!rs.dismissed_warning_banner && rs.valid_path) { std::wstring streamvr_config = get_steam_config_folder(rs); if (streamvr_config.empty()) { return; } std::wstring steamvr_settings = streamvr_config + L"\\steamvr.vrsettings"; bool file_valid = std::filesystem::exists(steamvr_settings.c_str()) && std::filesystem::is_regular_file(steamvr_settings.c_str()); if (file_valid) { rs.steamvr_settings_file = steamvr_settings; Json steamvr_json = utils.read_json(steamvr_settings); if (!steamvr_json.is_null()) { rs.flagged_settings.clear(); for (const std::pair& entry : ui.warned_settings) { const SteamVR_Warned_Settings& setting_type = entry.first; const SteamVR_Flagged_Setting& setting_details = entry.second; bool flagged = false; // If the warning has been ignored, don't attempt to flag it if (std::find(rs.ignored_steamvr_warnings.begin(), rs.ignored_steamvr_warnings.end(), static_cast(setting_type)) != rs.ignored_steamvr_warnings.end()) { continue; } if (steamvr_json[setting_details.json_category].is_null()) { // An absent setting category is missing which means the setting may be enabled by default if (setting_details.check_absence) { flagged = true; } else { continue; } } if (!flagged && !steamvr_json[setting_details.json_category][setting_details.steamvr_setting].is_null()) { std::string errstr; Json target_value = Json::parse(setting_details.steamvr_flag_value, errstr); std::string errstr2; Json found_value = Json::parse(steamvr_json[setting_details.json_category][setting_details.steamvr_setting].dump(), errstr2); if (errstr.empty() && errstr2.empty()) { // Found boolean or string value is equal to target and can be flagged // Found number value is greater (or less) than target and can be flagged if ((target_value.is_bool() && found_value.is_bool() && found_value.bool_value() == target_value.bool_value()) || (target_value.is_number() && found_value.is_number() && ((setting_details.number_value_greater && found_value.number_value() > target_value.number_value()) || (!setting_details.number_value_greater && found_value.number_value() < target_value.number_value()))) || (target_value.is_string() && found_value.is_string() && found_value.string_value() == found_value.string_value()) ) { flagged = true; } } } // An absent setting is missing which means it may be enabled by default else if (setting_details.check_absence) { flagged = true; } // Add the setting type to surface to the user if (flagged && std::find(rs.flagged_settings.begin(), rs.flagged_settings.end(), setting_type) == rs.flagged_settings.end()) { rs.flagged_settings.push_back(setting_type); } } return; } } } }); } bool Utils::read_settings_file(UI_Run_State& rs) { std::ifstream in(rs.settings_path); if (in.is_open()) { std::stringstream settings_buffer; settings_buffer << in.rdbuf(); in.close(); std::string settings_string = settings_buffer.str(); std::string error; Json json = Json::parse(settings_string.c_str(), error); if (error.length() > 0) { return false; } else { std::wstring_convert> converter; // Grandfather the old lighthouse setting if (!json["lighthouse_path"].is_null() && json["lighthouse_path"].is_string() && json["lighthouse_path"].string_value().find("lighthouse") != std::string::npos) { std::string lighthouse_path = json["lighthouse_path"].string_value() + "\\..\\.."; std::wstring wlighthouse_path = converter.from_bytes(lighthouse_path); // Use canonicolize to make it simpler (remove the \..) WCHAR fixedpath[MAX_PATH]; if (SUCCEEDED(PathCchCanonicalize(fixedpath, MAX_PATH, wlighthouse_path.c_str()))) { rs.steamvr_path = fixedpath; } } // Use "steamvr_path" json value if "lighthouse_path" doesn't exist else { std::string steam_path = json["steamvr_path"].string_value(); rs.steamvr_path = converter.from_bytes(steam_path); } std::string steam_config_path = json["steam_config_path"].string_value(); rs.steam_config_path = converter.from_bytes(steam_config_path); std::string rr = json["refresh_rate"].string_value(); for (auto& i : rs.refresh_rates) { if (i.second == rr) { rs.current_refresh_rate = i.first; } } rs.color[0] = (float)json["color_r"].number_value(); rs.color[1] = (float)json["color_g"].number_value(); rs.color[2] = (float)json["color_b"].number_value(); rs.brightness = std::clamp(json["brightness"].int_value(), UIConstants.brightness_min, UIConstants.brightness_max); rs.overdrive_brightness = std::clamp(json["overdrive_brightness"].int_value(), UIConstants.overdrive_brightness_min, UIConstants.overdrive_brightness_max); rs.fan_speed = std::clamp(json["fan_speed"].int_value(), UIConstants.fan_speed_min, UIConstants.fan_speed_max); rs.prox_offset = std::clamp(json["prox_offset"].int_value(), UIConstants.prox_min, UIConstants.prox_max); int ipd = std::clamp(json["ipd_mm"].int_value(), UIConstants.ipd_min, UIConstants.ipd_max); rs.applied_ipd = ipd > 0 ? ipd : 64; rs.adjusted_ipd = rs.applied_ipd; rs.overdrive = json["overdrive"].bool_value(); rs.vxr_auto_off = json["sleep_enabled"].bool_value(); rs.active_locale = (Locales)std::clamp(json["active_locale"].int_value(), 0, (int)Locales::Max); if (json["hmd_history"].is_array()) { for (const auto& element : json["hmd_history"].array_items()) { if (element.is_string()) { rs.hmd_serial_history.push_back(element.string_value()); } } } if (json["ignored_warnings"].is_array()) { for (const auto& element : json["ignored_warnings"].array_items()) { if (element.is_number()) { rs.ignored_steamvr_warnings.push_back(element.int_value()); } } } if (rs.overdrive) { rs.slider_brightness = rs.overdrive_brightness; } else { rs.slider_brightness = rs.brightness; } return true; } } else { return false; } } bool Utils::write_settings_file(UI_Run_State& rs) { std::wstring_convert> converter; std::string steamvr_path = converter.to_bytes(rs.steamvr_path); std::string steam_config_path = converter.to_bytes(rs.steam_config_path); Json settings = Json::object { { "active_locale", (int)rs.active_locale }, { "steamvr_path", steamvr_path }, { "steam_config_path", steam_config_path }, { "refresh_rate", rs.refresh_rates.at(rs.current_refresh_rate)}, { "color_r", rs.color[0] }, { "color_g", rs.color[1] }, { "color_b", rs.color[2] }, { "brightness", rs.brightness }, { "overdrive_brightness", rs.overdrive_brightness }, { "fan_speed", rs.fan_speed }, { "overdrive", rs.overdrive }, { "hmd_history", rs.hmd_serial_history }, { "ignored_warnings", rs.ignored_steamvr_warnings }, { "ipd_mm", rs.applied_ipd }, { "prox_offset", rs.prox_offset }, { "sleep_enabled", rs.vxr_auto_off } }; std::ofstream settings_output = std::ofstream(rs.settings_path); if (settings_output.is_open()) { settings_output << settings.dump(); settings_output.flush(); settings_output.close(); return true; } else { return false; } } void Utils::update_ipd(UI_Run_State& rs) { if (hid_handler.tundra_serial.empty()) { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_NO_SERIAL).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); return; } ui.changing_ipd.store(true); if (steam_change_thread.joinable()) { steam_change_thread.join(); } steam_change_thread = std::thread([&]() { ui.ipd_progress.store(0); // On first run we want to grab a copy of the config for backup. std::wstring headset_serial(hid_handler.tundra_serial.begin(), hid_handler.tundra_serial.end()); std::wstring backup_file_name_w = L"lighthouse_config_backup_" + headset_serial + L".json"; std::string backup_file_name = "lighthouse_config_backup_" + hid_handler.tundra_serial + ".json"; std::string serial_select_command = "serial " + hid_handler.tundra_serial + "\r\n"; // On first run we want to grab a copy of the config for backup. bool first_run = !std::filesystem::exists(backup_file_name); if (first_run) { // Download config file for backup. std::string command = "downloadconfig " + backup_file_name + "\r\n"; if (write_to_stdin(serial_select_command, rs.exe_path, command, NULL)) { if (!is_valid_json(backup_file_name_w, 10, 100)) { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_NO_BACKUP_FOUND) + L" (0x1)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.ipd_progress.store(100); ui.changing_ipd.store(false); DeleteFile(backup_file_name_w.c_str()); return; } } else { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_NO_BACKUP_FOUND) + L" (0x2)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.ipd_progress.store(100); ui.changing_ipd.store(false); return; } } ui.ipd_progress.store(25); // Delete any old config files. DeleteFile(L"lighthouse_config_new.json"); // Download config file. std::string command = "downloadconfig lighthouse_config_new.json\r\n"; if (write_to_stdin(serial_select_command, rs.exe_path, command, NULL)) { std::ifstream in("lighthouse_config_new.json"); int iterations = 0; // Wait for config file to be downloaded. while (!in.is_open() && iterations < 10) { Sleep(500); in.open("lighthouse_config_new.json"); ++iterations; } // Replace necessary configuration. if (in.is_open()) { std::stringstream config_buffer; config_buffer << in.rdbuf(); in.close(); std::string config_string = config_buffer.str(); // Make sure this is an HMD config file before continuing. if (config_string.find("\"device_class\": \"hmd\"") == std::string::npos || config_string.find("\"direct_mode_edid_pid\": \"1234\"") == std::string::npos || config_string.find("\"direct_mode_edid_vid\": \"BIG\"") == std::string::npos) { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_READING_HMD_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.ipd_progress.store(100); ui.changing_ipd.store(false); return; } ui.ipd_progress.store(50); // Validate the downloaded json before continuing if (!is_valid_json(L"lighthouse_config_new.json")) { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_VALIDATING_CONFIG) + L" (0x1)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.ipd_progress.store(100); ui.changing_ipd.store(false); return; } // Set a new IPD value size_t beginning = config_string.find("\"default_mm\""); if (beginning != std::string::npos) { // Find the start of the value size_t value_start = config_string.find(":", beginning) + 1; while (value_start < config_string.size() && isspace(config_string[value_start])) { value_start++; } // Find the end of the value size_t value_end = value_start; while (value_end < config_string.size() && (isdigit(config_string[value_end]) || config_string[value_end] == '.' || config_string[value_end] == '-')) { value_end++; } config_string.replace(value_start, value_end - value_start, std::to_string(rs.applied_ipd)); } else { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_NO_IPD_FIELD).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.ipd_progress.store(100); ui.changing_ipd.store(false); return; } std::ofstream out("lighthouse_config_new.json", std::ios::trunc); if (out.is_open()) { out << config_string; out.flush(); out.close(); // Validate again after modifying if (!is_valid_json(L"lighthouse_config_new.json")) { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_VALIDATING_CONFIG) + L" (0x2)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.ipd_progress.store(100); ui.changing_ipd.store(false); return; } // Upload the new config. std::string command = "uploadconfig lighthouse_config_new.json\r\n"; if (write_to_stdin(serial_select_command, rs.exe_path, command, NULL)) { // Successful upload. ui.ipd_progress.store(75); Sleep(500); } else { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_UPLOAD_NEW_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.ipd_progress.store(100); ui.changing_ipd.store(false); return; } } else { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_FAILED_STAGED_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.ipd_progress.store(100); ui.changing_ipd.store(false); return; } } else { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_FAILED_DOWNLOAD_CONFIG) + L" (0x1)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.ipd_progress.store(100); ui.changing_ipd.store(false); return; } } else { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_FAILED_DOWNLOAD_CONFIG) + L" (0x2)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.ipd_progress.store(100); ui.changing_ipd.store(false); return; } ui.ipd_progress.store(100); ui.changing_ipd.store(false); }); } void Utils::update_refresh_rate(UI_Run_State& rs) { if (hid_handler.tundra_serial.empty()) { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_NO_SERIAL).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); return; } ui.changing_refresh.store(true); // Update the EDID switch. This runs in the HID thread, so we // should be able to do it simultaneously. switch (rs.current_refresh_rate) { case Refresh_Rate::_75hz: rs.rate_n = 0; // sets both 75Hz and 90Hz. Default EDID break; case Refresh_Rate::_90hz: rs.rate_n = 1; // sets only 90Hz break; case Refresh_Rate::_72hz: rs.rate_n = 2; // sets only 72Hz break; } if (steam_change_thread.joinable()) { steam_change_thread.join(); } steam_change_thread = std::thread([&]() { ui.refresh_progress.store(0); // On first run we want to grab a copy of the config for backup. std::wstring headset_serial(hid_handler.tundra_serial.begin(), hid_handler.tundra_serial.end()); std::wstring backup_file_name_w = L"lighthouse_config_backup_" + headset_serial + L".json"; std::string backup_file_name = "lighthouse_config_backup_" + hid_handler.tundra_serial + ".json"; std::string serial_select_command = "serial " + hid_handler.tundra_serial + "\r\n"; // On first run we want to grab a copy of the config for backup. bool first_run = !std::filesystem::exists(backup_file_name); if (first_run) { // Download config file for backup. std::string command = "downloadconfig " + backup_file_name + "\r\n"; if (write_to_stdin(serial_select_command, rs.exe_path, command, NULL)) { if (!is_valid_json(backup_file_name_w, 10, 100)) { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_NO_BACKUP_FOUND) + L" (0x1)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.refresh_progress.store(100); ui.changing_refresh.store(false); DeleteFile(backup_file_name_w.c_str()); return; } } else { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_NO_BACKUP_FOUND) + L" (0x2)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.refresh_progress.store(100); ui.changing_refresh.store(false); return; } } ui.refresh_progress.store(25); // Delete any old config files. DeleteFile(L"lighthouse_config_new.json"); // Download config file. std::string command = "downloadconfig lighthouse_config_new.json\r\n"; if (write_to_stdin(serial_select_command, rs.exe_path, command, NULL)) { std::ifstream in("lighthouse_config_new.json"); int iterations = 0; // Wait for config file to be downloaded. while (!in.is_open() && iterations < 10) { Sleep(500); in.open("lighthouse_config_new.json"); ++iterations; } // Replace necessary configuration. if (in.is_open()) { std::stringstream config_buffer; config_buffer << in.rdbuf(); in.close(); std::string config_string = config_buffer.str(); // Make sure this is an HMD config file before continuing. if (config_string.find("\"device_class\": \"hmd\"") == std::string::npos || config_string.find("\"direct_mode_edid_pid\": \"1234\"") == std::string::npos || config_string.find("\"direct_mode_edid_vid\": \"BIG\"") == std::string::npos) { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_READING_HMD_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.refresh_progress.store(100); ui.changing_refresh.store(false); return; } ui.refresh_progress.store(50); // Validate the downloaded json before continuing if (!is_valid_json(L"lighthouse_config_new.json")) { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_VALIDATING_CONFIG) + L" (0x1)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.refresh_progress.store(100); ui.changing_refresh.store(false); return; } // Replace height. size_t beginning = config_string.find("\"eye_target_height_in_pixels\""); if (rs.current_refresh_rate == Refresh_Rate::_75hz || rs.current_refresh_rate == Refresh_Rate::_72hz) { // 75hz std::string new_height = "\"eye_target_height_in_pixels\": 2544"; config_string.replace(config_string.begin() + beginning, config_string.begin() + beginning + new_height.length(), new_height.c_str()); } else if (rs.current_refresh_rate == Refresh_Rate::_90hz) { // 90hz std::string new_height = "\"eye_target_height_in_pixels\": 1920"; config_string.replace(config_string.begin() + beginning, config_string.begin() + beginning + new_height.length(), new_height.c_str()); } // Replace width. beginning = config_string.find("\"eye_target_width_in_pixels\""); if (rs.current_refresh_rate == Refresh_Rate::_75hz || rs.current_refresh_rate == Refresh_Rate::_72hz) { // 75hz std::string new_width = "\"eye_target_width_in_pixels\": 2544"; config_string.replace(config_string.begin() + beginning, config_string.begin() + beginning + new_width.length(), new_width.c_str()); } else if (rs.current_refresh_rate == Refresh_Rate::_90hz) { // 90hz std::string new_width = "\"eye_target_width_in_pixels\": 1920"; config_string.replace(config_string.begin() + beginning, config_string.begin() + beginning + new_width.length(), new_width.c_str()); } // Write to a new config file. std::ofstream out("lighthouse_config_new.json", std::ios::trunc); if (out.is_open()) { out << config_string; out.flush(); out.close(); // Validate again after modifying if (!is_valid_json(L"lighthouse_config_new.json")) { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_VALIDATING_CONFIG) + L" (0x2)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.refresh_progress.store(100); ui.changing_refresh.store(false); return; } // Upload the new config. std::string command = "uploadconfig lighthouse_config_new.json\r\n"; if (write_to_stdin(serial_select_command, rs.exe_path, command, NULL)) { // Successful upload. ui.refresh_progress.store(75); Sleep(500); } else { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_UPLOAD_NEW_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.refresh_progress.store(100); ui.changing_refresh.store(false); return; } } else { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_FAILED_STAGED_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.refresh_progress.store(100); ui.changing_refresh.store(false); return; } } else { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_FAILED_DOWNLOAD_CONFIG) + L" (0x1)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.refresh_progress.store(100); ui.changing_refresh.store(false); return; } } else { MessageBox(glfwGetWin32Window(ui.window), (LWSTR(StringID::ERROR_FAILED_DOWNLOAD_CONFIG) + L" (0x2)").c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.refresh_progress.store(100); ui.changing_refresh.store(false); return; } ui.refresh_progress.store(100); ui.changing_refresh.store(false); // Operation successful. Now set the EDID ui.set_current_refresh.store(rs.rate_n); std::vector new_edid_config; std::string edid_val; edid_val.push_back(rs.rate_n); // 75Hz = 0, which is both resolutions // 90Hz = 1, which only allows 90Hz new_edid_config.push_back(std::make_tuple(Config_Tags::EDID_Switch, edid_val)); // Save update. write_mem_config(hid_handler, rs.config_memory_backup_path, new_edid_config); }); } void Utils::upload_lighthouse_config(UI_Run_State& rs) { if (!rs.valid_path) { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_FINDING_LIGHTHOUSE_CONSOLE).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); return; } if (hid_handler.tundra_serial.empty()) { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_NO_SERIAL).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); return; } ui.uploading_lighthouse_config.store(true); if (steam_change_thread.joinable()) { steam_change_thread.join(); } steam_change_thread = std::thread([&]() { // On first run we want to grab a copy of the config for backup. std::wstring headset_serial(hid_handler.tundra_serial.begin(), hid_handler.tundra_serial.end()); std::wstring backup_file_name_w = L"lighthouse_config_backup_" + headset_serial + L".json"; std::string backup_file_name = "lighthouse_config_backup_" + hid_handler.tundra_serial + ".json"; std::string serial_select_command = "serial " + hid_handler.tundra_serial + "\r\n"; // On first run we want to grab a copy of the config for backup. bool first_run = !std::filesystem::exists(backup_file_name); if (first_run) { // Download config file for backup. std::string command = "downloadconfig " + backup_file_name + "\r\n"; write_to_stdin(serial_select_command, rs.exe_path, command, NULL); } std::ifstream in(rs.lighthouse_restore_path); int iterations = 0; // Make multiple attempts at opening the selected file. while (!in.is_open() && iterations < 10) { Sleep(500); in.open(rs.lighthouse_restore_path); ++iterations; } if (in.is_open()) { std::stringstream config_buffer; config_buffer << in.rdbuf(); in.close(); std::string config_string = config_buffer.str(); // Make sure this is an HMD config file before continuing. if (config_string.find("\"device_class\": \"hmd\"") == std::string::npos || config_string.find("\"direct_mode_edid_pid\": \"1234\"") == std::string::npos || config_string.find("\"direct_mode_edid_vid\": \"BIG\"") == std::string::npos) { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_FAILED_VALIDATING_CONFIG_LT).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.uploading_lighthouse_config.store(false); return; } // Validate the json before continuing if (!is_valid_json(rs.lighthouse_restore_path)) { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_FAILED_VALIDATING_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.uploading_lighthouse_config.store(false); return; } // Upload the new config. std::string command = "uploadconfig \"" + string_wide_to_narrow(rs.lighthouse_restore_path) + "\"\r\n"; if (write_to_stdin(serial_select_command, rs.exe_path, command, NULL)) { // Successful upload. Sleep(1000); ui.set_steamvr_settings.store(true); ui.uploading_lighthouse_config.store(false); } else { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_UPLOAD_NEW_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.uploading_lighthouse_config.store(false); return; } } else { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_FAILED_OPENING_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); ui.uploading_lighthouse_config.store(false); return; } }); } void Utils::enter_dfu_mode(UI_Run_State& rs) { hid_handler.entering_dfu.store(true); rs.initiated_dfu_request = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); hid_handler.command_fpga_switch(); } void Utils::start_dfu_update(UI_Run_State& rs) { auto [exists, driverKey] = hid_handler.in_dfu(); hid_handler.device_state.store(Beyond_Device_State::EyetrackingDFU); if (!exists) { rs.fw.clear_dfu_status(); rs.dfu_update_requested = true; utils.enter_dfu_mode(rs); } else { rs.fw.load_dfu_firmware(hid_handler, rs.dfu_firmware_path); } } std::string Utils::get_device_type() { if (hid_handler.hmd_serial.empty()) { return ""; } // Models with added extensions if (hid_handler.hmd_serial.substr(0, 4) == "BS2E") { return "BS2E"; } // Regular models return hid_handler.hmd_serial.substr(0, 3); } std::tuple Utils::split_headset_firmware() { if (hid_handler.firmware_version.empty()) { return std::make_tuple(0, 0, 0); } // Extract version from string, convert to ints std::string major_version = hid_handler.firmware_version.substr(0, hid_handler.firmware_version.find('.')); int major_version_int = std::stoi(major_version); std::string minor_version = hid_handler.firmware_version.substr(hid_handler.firmware_version.find('.') + 1, hid_handler.firmware_version.find('.', hid_handler.firmware_version.find('.') + 1) - hid_handler.firmware_version.find('.') - 1); int minor_version_int = std::stoi(minor_version); std::string patch_version = hid_handler.firmware_version.substr(hid_handler.firmware_version.find('.', hid_handler.firmware_version.find('.') + 1) + 1); int patch_version_int = std::stoi(patch_version); return std::make_tuple(major_version_int, minor_version_int, patch_version_int); } std::wstring Utils::open_file_select(const wchar_t* filter) { HWND hwnd = glfwGetWin32Window(ui.window); if (hwnd) { OPENFILENAME of_instance; wchar_t szFile[MAX_PATH] = L""; ZeroMemory(&of_instance, sizeof(of_instance)); of_instance.lStructSize = sizeof(of_instance); of_instance.hwndOwner = hwnd; of_instance.nMaxFile = MAX_PATH; of_instance.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; of_instance.lpstrFilter = filter; of_instance.nFilterIndex = 1; of_instance.lpstrFile = szFile; // Display the Open dialog box if (GetOpenFileNameW(&of_instance)) { return of_instance.lpstrFile; } } return L""; } void Utils::find_system_locale(UI_Run_State& rs) { hid_handler.log << "Getting system locale" << std::endl; LANGID lang = GetUserDefaultUILanguage(); char lid[LOCALE_NAME_MAX_LENGTH]; LCID lcid = MAKELCID(lang, SORT_DEFAULT); GetLocaleInfoA(lcid, LOCALE_SISO639LANGNAME, lid, sizeof(lid)); hid_handler.log << "Found system locale: " << lid << std::endl; if (strcmp(lid, "en") == 0) { rs.active_locale = Locales::En; } if (strcmp(lid, "jp") == 0) { rs.active_locale = Locales::Jp; } } Lang_Setting Utils::get_locale_settings(UI_Run_State& rs) { Locales locale_next = rs.active_locale; // Fallback to en if invalid if ((int)rs.active_locale >= (int)Locales::Max || rs.active_locale == Locales::None) { locale_next = Locales::En; } return ui.languages.at(locale_next); } void Utils::shutdown() { if (steam_change_thread.joinable()) { steam_change_thread.join(); } if (steamvr_check.joinable()) { steamvr_check.join(); } }