Properly rewrite ShortcutsVDF parser

pull/175/head
Peter Repukat 2 years ago
parent 7e9e6f8e96
commit 288bba1436

3
.gitmodules vendored

@ -28,3 +28,6 @@
[submodule "deps/traypp"]
path = deps/traypp
url = https://github.com/Soundux/traypp.git
[submodule "deps/fifo_map"]
path = deps/fifo_map
url = git@github.com:nlohmann/fifo_map.git

@ -68,7 +68,7 @@
<AdditionalOptions>/Zc:__cplusplus /Zc:twoPhase- %(AdditionalOptions)</AdditionalOptions>
<CompileAsWinRT>false</CompileAsWinRT>
<PreprocessorDefinitions>NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\deps\WinReg;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\deps\WinReg;..\deps\fifo_map\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Manifest>
<EnableDpiAwareness>true</EnableDpiAwareness>
@ -90,7 +90,7 @@
<AdditionalOptions>/Zc:__cplusplus /Zc:zwoPhase- /permissive- %(AdditionalOptions)</AdditionalOptions>
<CompileAsWinRT>false</CompileAsWinRT>
<PreprocessorDefinitions>NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\deps\WinReg;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\deps\WinReg;..\deps\fifo_map\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Manifest>
<EnableDpiAwareness>true</EnableDpiAwareness>
@ -155,7 +155,6 @@
<ClInclude Include="resource.h" />
<ClInclude Include="ShortcutsVDF.h" />
<ClInclude Include="UWPFetch.h" />
<ClInclude Include="VDFParser.h" />
<ClInclude Include="WinEventFilter.h" />
</ItemGroup>
<ItemGroup>

@ -75,9 +75,6 @@
<ClInclude Include="WinEventFilter.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="VDFParser.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="UWPFetch.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -87,6 +84,9 @@
<ClInclude Include="ExeImageProvider.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ShortcutsVDF.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Xml Include="manifest.xml">
@ -100,7 +100,7 @@
</ItemGroup>
<ItemGroup>
<Image Include="$(SolutionDir)\GloSC_Icon.ico">
<Filter>Resource Files</Filter>
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
</Project>

@ -51,8 +51,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,0,7,1021003100876
PRODUCTVERSION 0,0,7,1021003100876
FILEVERSION 0,0,8,001000606002
PRODUCTVERSION 0,0,8,001000606002
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -69,12 +69,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "Peter Repukat - FlatspotSoftware"
VALUE "FileDescription", "GlosSI - Config"
VALUE "FileVersion", "0.0.7.1-21-g31ee876"
VALUE "FileVersion", "0.0.8.0-1-gd6c6ef2"
VALUE "InternalName", "GlosSIConfig"
VALUE "LegalCopyright", "Copyright (C) 2021 Peter Repukat - FlatspotSoftware"
VALUE "OriginalFilename", "GlosSIConfig.exe"
VALUE "ProductName", "GlosSI"
VALUE "ProductVersion", "0.0.7.1-21-g31ee876"
VALUE "ProductVersion", "0.0.8.0-1-gd6c6ef2"
END
END
BLOCK "VarFileInfo"
@ -308,6 +308,254 @@ IDI_ICON1 ICON "..\GloSC_Icon.ico"

@ -0,0 +1,493 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <any>
#include <iostream>
#include <string>
#include <vector>
#include <filesystem>
#include <fstream>
#include <regex>
#include <ranges>
#include <fifo_map.hpp>
namespace VDFParser {
namespace internal {
constexpr unsigned int str2int(const char* str, int h = 0)
{
return !str[h] ? 5381 : (str2int(str, h + 1) * 33) ^ str[h];
}
}
namespace crc {
template <typename CONT>
uint32_t calculate_crc(CONT container)
{
uint32_t crc32_table[256];
for (uint32_t i = 0; i < 256; i++) {
uint32_t ch = i;
uint32_t crc = 0;
for (size_t j = 0; j < 8; j++) {
const uint32_t b = (ch ^ crc) & 1;
crc >>= 1;
if (b)
crc = crc ^ 0xEDB88320;
ch >>= 1;
}
crc32_table[i] = crc;
}
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < container.size(); i++) {
const char ch = container.data()[i];
const uint32_t t = (ch ^ crc) & 0xFF;
crc = (crc >> 8) ^ crc32_table[t];
}
return ~crc;
}
} // namespace crc
enum VDFTypeId {
Map = 0,
String,
Number,
EndMarker = 0x08
};
static constexpr const char k_appid[] = {"appid"};
static constexpr const char k_appname[] = {"appname"};
static constexpr const char k_exe[] = {"exe"};
static constexpr const char k_StartDir[] = {"StartDir"};
static constexpr const char k_icon[] = {"icon"};
static constexpr const char k_ShortcutPath[] = {"ShortcutPath"};
static constexpr const char k_LaunchOptions[] = {"LaunchOptions"};
static constexpr const char k_IsHidden[] = {"IsHidden"};
static constexpr const char k_AllowDesktopConfig[] = {"AllowDesktopConfig"};
static constexpr const char k_AllowOverlay[] = {"AllowOverlay"};
static constexpr const char k_openvr[] = {"openvr"};
static constexpr const char k_Devkit[] = {"Devkit"};
static constexpr const char k_DevkitGameID[] = {"DevkitGameID"};
static constexpr const char k_DevkitOverrideAppID[] = {"DevkitOverrideAppID"};
static constexpr const char k_LastPlayTime[] = {"LastPlayTime"};
static constexpr const char k_FlatpakAppID[] = {"FlatpakAppID"};
static constexpr const char k_tags[] = {"tags"};
static const std::string SHORTCUTS_IDENTIFIER = "shortcuts";
static inline const std::vector<std::string> VDF_KEYS = {
k_appid,
k_appname,
k_exe,
k_StartDir,
k_icon,
k_ShortcutPath,
k_LaunchOptions,
k_IsHidden,
k_AllowDesktopConfig,
k_AllowOverlay,
k_openvr,
k_Devkit,
k_DevkitGameID,
k_DevkitOverrideAppID,
k_LastPlayTime,
k_FlatpakAppID,
k_tags
};
struct VDFValue {
VDFTypeId type = EndMarker;
std::any value{};
[[nodiscard]] std::string to_json() const
{
if (!value.has_value()) {
return "null";
}
switch (type) {
case Number:
return std::to_string(std::any_cast<uint32_t>(value));
case String:
return "\"" + std::regex_replace(std::regex_replace(std::any_cast<std::string>(value), std::regex(R"(\\)"), R"(\\)"), std::regex(R"(")"), R"(\")") + "\"";
case Map: {
const auto& map = std::any_cast<nlohmann::fifo_map<std::string, VDFValue>>(value);
std::string res = "{\n";
for (const auto& [key, v] : map) {
res += "\"" + key + "\": " + v.to_json() + ",\n";
}
if (res.ends_with(",\n")) {
res.pop_back();
res.pop_back();
}
return res + "\n}";
}
case EndMarker:
// WTF?!
return "\"EndMarker\"";
default:
return "null";
}
}
operator std::string() const
{
return to_json();
}
};
static inline const nlohmann::fifo_map<std::string, VDFValue> DEFAULT_SHORTCUT_MAP = {
{ k_appid, {VDFTypeId::Number, 0}},
{ k_appname, {VDFTypeId::String, ""}},
{ k_exe, {VDFTypeId::String, "\"\""}},
{ k_StartDir, {VDFTypeId::String, "\"\""}},
{ k_icon, {VDFTypeId::String, "\"\""}},
{ k_ShortcutPath, {VDFTypeId::String, "\"\""}},
{ k_LaunchOptions, {VDFTypeId::String, ""}},
{ k_IsHidden, {VDFTypeId::Number, ""}},
{ k_AllowDesktopConfig, {VDFTypeId::Number, 0}},
{ k_AllowOverlay, {VDFTypeId::Number, 1}},
{ k_openvr, {VDFTypeId::Number, 0}},
{ k_Devkit, {VDFTypeId::Number, 0}},
{ k_DevkitGameID, {VDFTypeId::String, ""}},
{ k_DevkitOverrideAppID, {VDFTypeId::Number, 0}},
{ k_LastPlayTime, {VDFTypeId::Number, ""}},
{ k_FlatpakAppID, {VDFTypeId::String, ""}},
{ k_tags, {VDFTypeId::Map, nlohmann::fifo_map < std::string, VDFValue >()}},
};
struct Shortcut {
uint32_t appid{};
std::string appname;
std::string exe;
std::string StartDir;
std::string icon;
std::string ShortcutPath;
std::string LaunchOptions;
uint32_t IsHidden{};
uint32_t AllowDesktopConfig{};
uint32_t AllowOverlay{};
uint32_t openvr{};
uint32_t Devkit{};
std::string DevkitGameID;
uint32_t DevkitOverrideAppID{};
uint32_t LastPlayTime{};
std::string FlatpakAppID;
std::vector<std::string> tags;
nlohmann::fifo_map<std::string, VDFValue> unsupported_keys;
Shortcut() = default;
explicit Shortcut(const nlohmann::fifo_map<std::string, VDFValue>& vdf_map) : Shortcut()
{
for (const auto& [key, value] : (vdf_map.empty() ? DEFAULT_SHORTCUT_MAP : vdf_map)) {
switch (value.type) {
case Number: {
switch (internal::str2int(key.c_str())) {
case internal::str2int(k_appid):
appid = std::any_cast<uint32_t>(value.value);
break;
case internal::str2int(k_IsHidden):
IsHidden = std::any_cast<uint32_t>(value.value);
break;
case internal::str2int(k_AllowDesktopConfig):
AllowDesktopConfig = std::any_cast<uint32_t>(value.value);
break;
case internal::str2int(k_AllowOverlay):
AllowOverlay = std::any_cast<uint32_t>(value.value);
break;
case internal::str2int(k_openvr):
openvr = std::any_cast<uint32_t>(value.value);
break;
case internal::str2int(k_Devkit):
Devkit = std::any_cast<uint32_t>(value.value);
break;
case internal::str2int(k_DevkitOverrideAppID):
DevkitOverrideAppID = std::any_cast<uint32_t>(value.value);
break;
case internal::str2int(k_LastPlayTime):
LastPlayTime = std::any_cast<uint32_t>(value.value);
break;
default:
unsupported_keys[key] = value;
break;
}
break;
case String: {
switch (internal::str2int(key.c_str())) {
case internal::str2int(k_appname):
appname = std::any_cast<std::string>(value.value);
break;
case internal::str2int(k_exe):
exe = std::any_cast<std::string>(value.value);
break;
case internal::str2int(k_StartDir):
StartDir = std::any_cast<std::string>(value.value);
break;
case internal::str2int(k_icon):
icon = std::any_cast<std::string>(value.value);
break;
case internal::str2int(k_ShortcutPath):
ShortcutPath = std::any_cast<std::string>(value.value);
break;
case internal::str2int(k_LaunchOptions):
LaunchOptions = std::any_cast<std::string>(value.value);
break;
case internal::str2int(k_DevkitGameID):
DevkitGameID = std::any_cast<std::string>(value.value);
break;
case internal::str2int(k_FlatpakAppID):
FlatpakAppID = std::any_cast<std::string>(value.value);
break;
default:
unsupported_keys[key] = value;
break;
}
break;
}
case Map: {
switch (internal::str2int(key.c_str())) {
case internal::str2int(k_tags): {
for (const auto& [type, tag] : std::any_cast<nlohmann::fifo_map<std::string, VDFValue>>(value.value) | std::views::values) {
if (type == String) {
tags.push_back(std::any_cast<std::string>(tag));
}
}
break;
}
default:
unsupported_keys[key] = value;
break;
}
break;
}
}
}
}
}
[[nodiscard]] uint32_t calculateAppId() const
{
const auto checksum = crc::calculate_crc(exe + appname);
return checksum | 0x80000000;
}
operator VDFValue() const
{
nlohmann::fifo_map<std::string, VDFValue> value;
value[k_appid] = {Number, appid ? appid : calculateAppId()};
value[k_appname] = {String, appname};
value[k_exe] = {String, exe};
value[k_StartDir] = {String, StartDir};
value[k_icon] = {String, icon};
value[k_ShortcutPath] = {String, ShortcutPath};
value[k_LaunchOptions] = {String, LaunchOptions};
value[k_IsHidden] = {Number, IsHidden};
value[k_AllowDesktopConfig] = {Number, AllowDesktopConfig};
value[k_AllowOverlay] = {Number, AllowOverlay};
value[k_openvr] = {Number, openvr};
value[k_Devkit] = {Number, Devkit};
value[k_DevkitGameID] = {String, DevkitGameID};
value[k_DevkitOverrideAppID] = {Number, DevkitOverrideAppID};
value[k_LastPlayTime] = {Number, LastPlayTime};
value[k_FlatpakAppID] = {String, FlatpakAppID};
nlohmann::fifo_map<std::string, VDFValue> tag_map;
for (size_t i = 0; i < tags.size(); i++) {
tag_map[std::to_string(i)] = {String,tags[i]};
}
value[k_tags] = {Map, tag_map};
for (const auto& [key, v] : unsupported_keys) {
value[key] = v;
}
return {Map, value};
}
};
class Parser {
private:
static inline std::ifstream ifile;
static inline std::ofstream ofile;
template <typename typ>
static inline auto readVDFValue()
{
uint8_t buff[sizeof(typ)];
ifile.read((char*)buff, sizeof(typ));
return *reinterpret_cast<typ*>(buff);
}
template <>
static inline auto readVDFValue<std::string>()
{
std::string str;
char ch = '\x0';
do {
if (ifile.eof()) {
return str;
}
ifile.read(&ch, sizeof(char));
if (ch != '\x0')
str.push_back(ch);
} while (ch != '\x0');
return str;
}
template <>
static inline auto readVDFValue<nlohmann::fifo_map<std::string, VDFValue>>()
{
auto res = nlohmann::fifo_map<std::string, VDFValue>();
while (true) {
const auto& [key, value] = readVDFValue();
if (value.type == EndMarker) {
return res;
}
res[key] = value;
}
}
static inline std::pair<std::string, VDFValue> readVDFValue()
{
auto res = std::pair<std::string, VDFValue>("", VDFValue(EndMarker, nullptr));
if (ifile.eof()) {
return res;
}
const auto tid = static_cast<VDFTypeId>(readVDFValue<uint8_t>());
if (tid == EndMarker) {
return res;
}
res.second.type = tid;
res.first = readVDFValue<std::string>();
switch (tid) {
case VDFTypeId::Map:
res.second.value = readVDFValue<nlohmann::fifo_map<std::string, VDFValue>>();
break;
case VDFTypeId::Number:
res.second.value = readVDFValue<uint32_t>();
break;
case VDFTypeId::String:
res.second.value = readVDFValue<std::string>();
break;
default:
throw std::exception("VDF: Unknown TypeID");
break;
}
return res;
}
template <typename typ>
static inline auto writeVDFValue(typ v)
{
ofile.write((char*)&v, sizeof(typ));
}
template <>
static inline auto writeVDFValue<std::string>(std::string v)
{
ofile.write(v.data(), v.length());
ofile.write("\x00", 1);
}
template <>
static inline auto writeVDFValue<nlohmann::fifo_map<std::string, VDFValue>>(nlohmann::fifo_map<std::string, VDFValue> v)
{
for (const auto& pair : v) {
writeVDFValue(pair);
}
ofile.write("\x08", 1);
}
static inline void writeVDFValue(const std::pair<const std::string, VDFValue>& value)
{
ofile.write((char*)(&value.second.type), 1);
writeVDFValue(value.first);
switch (value.second.type) {
case Map:
writeVDFValue(std::any_cast<nlohmann::fifo_map<std::string, VDFValue>>(value.second.value));
break;
case Number:
writeVDFValue(std::any_cast<uint32_t>(value.second.value));
break;
case String:
writeVDFValue(std::any_cast<std::string>(value.second.value));
break;
default:
throw std::exception("VDF: Unknown TypeID");
break;
}
}
public:
template <typename LogStream = std::ostream>
static inline std::vector<Shortcut> parseShortcuts(const std::filesystem::path& path, LogStream l = std::cout)
{
ifile.open(path, std::ios::binary | std::ios::in);
if (!ifile.is_open()) {
return {};
}
const auto& shortcutsVDF = readVDFValue();
ifile.close();
if (shortcutsVDF.second.type != Map || shortcutsVDF.first != SHORTCUTS_IDENTIFIER) {
throw std::exception("invalid shortcuts file!");
}
const auto& v = std::any_cast<nlohmann::fifo_map<std::string, VDFValue>>(shortcutsVDF.second.value);
std::vector<Shortcut> shortcuts;
for (const auto& [type, sc] : v | std::views::values) {
if (type != Map) {
// throw std::exception("invalid shortcuts file!");
// TODO: warn unsupported
l << "unsupported or invalid shortcuts-file!";
continue;
}
shortcuts.emplace_back(std::any_cast<nlohmann::fifo_map<std::string, VDFValue>>(sc));
}
return shortcuts;
}
template <typename LogStream = std::ostream>
static inline bool writeShortcuts(const std::filesystem::path& path, const std::vector<Shortcut>& shortcuts, LogStream l = std::cout)
{
const auto backupFileName = path.wstring() + L".bak";
if (std::filesystem::exists(path) && !std::filesystem::exists(backupFileName)) {
l << "No shortcuts backup detected... Creating now...";
const auto copied = std::filesystem::copy_file(path, backupFileName, std::filesystem::copy_options::update_existing);
l << "failed to copy shortcuts.vdf to backup!";
}
ofile.open(path.wstring(), std::ios::binary | std::ios::out);
if (!ofile.is_open()) {
return false;
}
nlohmann::fifo_map<std::string, VDFValue> shortcuts_map;
for (size_t i = 0; i < shortcuts.size(); i++) {
shortcuts_map[std::to_string(i)] = shortcuts[i];
}
writeVDFValue({SHORTCUTS_IDENTIFIER, {Map, shortcuts_map}});
ofile.write("\x08", 1);
ofile.close();
return true;
}
};
} // namespace VDFParser

@ -141,9 +141,9 @@ void UIModel::deleteTarget(int index)
bool UIModel::isInSteam(QVariant shortcut)
{
const auto map = shortcut.toMap();
for (auto& steam_shortcut : shortcuts_vdf_.shortcuts) {
if (map["name"].toString() == QString::fromStdString(steam_shortcut.appName.value)) {
if (QString::fromStdString(steam_shortcut.exe.value).toLower().contains("glossitarget.exe")) {
for (auto& steam_shortcut : shortcuts_vdf_) {
if (map["name"].toString() == QString::fromStdString(steam_shortcut.appname)) {
if (QString::fromStdString(steam_shortcut.exe).toLower().contains("glossitarget.exe")) {
return true;
}
}
@ -161,15 +161,13 @@ bool UIModel::addToSteam(QVariant shortcut, const QString& shortcutspath, bool f
const auto launch = map["launch"].toBool();
VDFParser::Shortcut vdfshortcut;
vdfshortcut.idx = shortcuts_vdf_.shortcuts.size();
vdfshortcut.appName.value = name.toStdString();
vdfshortcut.exe.value = ("\"" + appDir.absolutePath() + "/GlosSITarget.exe" + "\"").toStdString();
vdfshortcut.StartDir.value = (launch && !maybeLaunchPath.isEmpty()
vdfshortcut.appname = name.toStdString();
vdfshortcut.exe = ("\"" + appDir.absolutePath() + "/GlosSITarget.exe" + "\"").toStdString();
vdfshortcut.StartDir = (launch && !maybeLaunchPath.isEmpty()
? (std::string("\"") + std::filesystem::path(maybeLaunchPath.toStdString()).parent_path().string() + "\"")
: ("\"" + appDir.absolutePath() + "\"").toStdString());
vdfshortcut.appId.value = VDFParser::Parser::calculateAppId(vdfshortcut);
// ShortcutPath; default
vdfshortcut.LaunchOptions.value = (QString(name).replace(QRegularExpression("[\\\\/:*?\"<>|]"), "") + ".json").toStdString();
vdfshortcut.LaunchOptions = (QString(name).replace(QRegularExpression("[\\\\/:*?\"<>|]"), "") + ".json").toStdString();
// IsHidden; default
// AllowDesktopConfig; default
// AllowOverlay; default
@ -181,25 +179,18 @@ bool UIModel::addToSteam(QVariant shortcut, const QString& shortcutspath, bool f
auto maybeIcon = map["icon"].toString();
if (maybeIcon.isEmpty()) {
if (launch && !maybeLaunchPath.isEmpty())
vdfshortcut.icon.value =
vdfshortcut.icon =
"\"" + (is_windows_ ? QString(maybeLaunchPath).replace(QRegularExpression("\\/"), "\\").toStdString() : maybeLaunchPath.toStdString()) + "\"";
}
else {
vdfshortcut.icon.value =
vdfshortcut.icon =
"\"" + (is_windows_ ? QString(maybeIcon).replace(QRegularExpression("\\/"), "\\").toStdString() : maybeIcon.toStdString()) + "\"";
}
// Add installed locally and GlosSI tag
VDFParser::ShortcutTag locallyTag;
locallyTag.idx = 0;
locallyTag.value = "Installed locally";
vdfshortcut.tags.value.push_back(locallyTag);
vdfshortcut.tags.push_back("Installed locally");
vdfshortcut.tags.push_back("GlosSI");
VDFParser::ShortcutTag glossitag;
glossitag.idx = 1;
glossitag.value = "GlosSI";
vdfshortcut.tags.value.push_back(glossitag);
shortcuts_vdf_.shortcuts.push_back(vdfshortcut);
shortcuts_vdf_.push_back(vdfshortcut);
return writeShortcutsVDF(L"add", name.toStdWString(), shortcutspath.toStdWString(), from_cmd);
}
@ -220,16 +211,10 @@ bool UIModel::addToSteam(const QString& name, const QString& shortcutspath, bool
bool UIModel::removeFromSteam(const QString& name, const QString& shortcutspath, bool from_cmd)
{
qDebug() << "trying to remove " << name << " from steam";
auto& scuts = shortcuts_vdf_.shortcuts;
scuts.erase(std::remove_if(scuts.begin(), scuts.end(), [&name](const auto& shortcut) {
return shortcut.appName.value == name.toStdString();
}),
scuts.end());
for (int i = 0; i < scuts.size(); i++) {
if (scuts[i].idx != i) {
scuts[i].idx = i;
}
}
shortcuts_vdf_.erase(std::ranges::remove_if(shortcuts_vdf_, [&name](const auto& shortcut) {
return shortcut.appname == name.toStdString();
}).begin(),
shortcuts_vdf_.end());
return writeShortcutsVDF(L"remove", name.toStdWString(), shortcutspath.toStdWString(), from_cmd);
}
@ -271,7 +256,7 @@ bool UIModel::writeShortcutsVDF(const std::wstring& mode, const std::wstring& na
bool write_res;
try {
write_res = VDFParser::Parser::writeShortcuts(config_path, shortcuts_vdf_);
write_res = VDFParser::Parser::writeShortcuts(config_path, shortcuts_vdf_, qDebug());
}
catch (const std::exception& e) {
qDebug() << "Couldn't backup shortcuts file: " << e.what();
@ -413,7 +398,7 @@ void UIModel::parseShortcutVDF()
{
const std::filesystem::path config_path = std::wstring(getSteamPath()) + user_data_path_.toStdWString() + getSteamUserId() + shortcutsfile_.toStdWString();
try {
shortcuts_vdf_ = VDFParser::Parser::parseShortcuts(config_path);
shortcuts_vdf_ = VDFParser::Parser::parseShortcuts(config_path, qDebug());
}
catch (const std::exception& e) {
qDebug() << "Error parsing VDF: " << e.what();

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include "VDFParser.h"
#include "ShortcutsVDF.h"
#include <QJsonObject>
#include <QObject>
#include <QVariant>
@ -74,7 +74,7 @@ class UIModel : public QObject {
QVariantList targets_;
VDFParser::VDFFile shortcuts_vdf_;
std::vector<VDFParser::Shortcut> shortcuts_vdf_;
#ifdef _WIN32
bool is_windows_ = true;

@ -1,593 +0,0 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// "Shitty shortcuts.vdf Parser"<22>
#pragma once
#include <charconv>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <QDebug>
namespace VDFParser {
namespace crc {
template <typename CONT>
uint32_t calculate_crc(CONT container)
{
uint32_t crc32_table[256];
for (uint32_t i = 0; i < 256; i++) {
uint32_t ch = i;
uint32_t crc = 0;
for (size_t j = 0; j < 8; j++) {
uint32_t b = (ch ^ crc) & 1;
crc >>= 1;
if (b)
crc = crc ^ 0xEDB88320;
ch >>= 1;
}
crc32_table[i] = crc;
}
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < container.size(); i++) {
char ch = container.data()[i];
uint32_t t = (ch ^ crc) & 0xFF;
crc = (crc >> 8) ^ crc32_table[t];
}
return ~crc;
}
} // namespace crc
static constexpr const char k_appid[] = {"appid"};
static constexpr const char k_appname[] = {"appname"};
static constexpr const char k_exe[] = {"exe"};
static constexpr const char k_StartDir[] = {"StartDir"};
static constexpr const char k_icon[] = {"icon"};
static constexpr const char k_ShortcutPath[] = {"ShortcutPath"};
static constexpr const char k_LaunchOptions[] = {"LaunchOptions"};
static constexpr const char k_IsHidden[] = {"IsHidden"};
static constexpr const char k_AllowDesktopConfig[] = {"AllowDesktopConfig"};
static constexpr const char k_AllowOverlay[] = {"AllowOverlay"};
static constexpr const char k_openvr[] = {"openvr"};
static constexpr const char k_Devkit[] = {"Devkit"};
static constexpr const char k_DevkitGameID[] = {"DevkitGameID"};
static constexpr const char k_DevkitOverrideAppID[] = {"DevkitOverrideAppID"};
static constexpr const char k_LastPlayTime[] = {"LastPlayTime"};
static constexpr const char k_FlatpakAppID[] = {"FlatpakAppID"};
static constexpr const char k_tags[] = {"tags"};
enum VDFTypeId {
StringList = 0,
String,
Number,
};
template <const char* const keyname, typename type, const uint8_t _type_id>
struct VDFKeyPair {
VDFKeyPair() {}
explicit VDFKeyPair(type _value) : value(_value) {}
static constexpr uint8_t _TID = _type_id;
static constexpr const char* const _KEY = keyname;
const uint8_t type_id = _TID;
const char* const key = _KEY;
type value;
VDFKeyPair(const VDFKeyPair& other)
{
value = other.value;
};
VDFKeyPair(VDFKeyPair&& other)
{
value = std::move(other.value);
};
VDFKeyPair& operator=(const VDFKeyPair& other)
{
value = other.value;
return *this;
}
VDFKeyPair& operator=(VDFKeyPair&& other)
{
value = std::move(other.value);
return *this;
}
};
struct VDFIdx {
VDFIdx(){};
VDFIdx(const VDFIdx& other)
{
data = other.data;
};
VDFIdx(VDFIdx&& other)
{
data = std::move(other.data);
};
VDFIdx(int idx)
{
data = std::to_string(idx);
}
std::string data;
operator int() const
{
int res = 0;
std::from_chars(data.data(), data.data() + data.size(), res);
return res;
}
VDFIdx& operator=(const VDFIdx& other)
{
data = other.data;
return *this;
}
VDFIdx& operator=(VDFIdx&& other)
{
data = std::move(other.data);
return *this;
}
};
struct ShortcutTag {
ShortcutTag(){};
ShortcutTag(const ShortcutTag& other)
{
idx = other.idx;
value = other.value;
};
ShortcutTag(ShortcutTag&& other)
{
idx = std::move(other.idx);
value = std::move(other.value);
};
VDFIdx idx;
std::string value;
const uint16_t end_marker = 0x0808;
ShortcutTag& operator=(const ShortcutTag& other)
{
idx = other.idx;
value = other.value;
return *this;
}
ShortcutTag& operator=(ShortcutTag&& other)
{
idx = std::move(other.idx);
value = std::move(other.value);
return *this;
}
};
struct Shortcut {
VDFIdx idx;
VDFKeyPair<k_appid, uint32_t, VDFTypeId::Number> appId{0x000000};
VDFKeyPair<k_appname, std::string, VDFTypeId::String> appName{""};
VDFKeyPair<k_exe, std::string, VDFTypeId::String> exe{"\"\""}; // Qouted
VDFKeyPair<k_StartDir, std::string, VDFTypeId::String> StartDir{"\"\""}; // Qouted
VDFKeyPair<k_icon, std::string, VDFTypeId::String> icon{""}; // Qouted or empty
VDFKeyPair<k_ShortcutPath, std::string, VDFTypeId::String> ShortcutPath{""}; // Qouted or empty?
VDFKeyPair<k_LaunchOptions, std::string, VDFTypeId::String> LaunchOptions{""}; // UNQOUTED or empty
VDFKeyPair<k_IsHidden, uint32_t, VDFTypeId::Number> IsHidden{0};
VDFKeyPair<k_AllowDesktopConfig, uint32_t, VDFTypeId::Number> AllowDesktopConfig{1};
VDFKeyPair<k_AllowOverlay, uint32_t, VDFTypeId::Number> AllowOverlay{1};
VDFKeyPair<k_openvr, uint32_t, VDFTypeId::Number> openvr{0};
VDFKeyPair<k_Devkit, uint32_t, VDFTypeId::Number> Devkit{0};
VDFKeyPair<k_DevkitGameID, std::string, VDFTypeId::String> DevkitGameID{""};
VDFKeyPair<k_DevkitOverrideAppID, uint32_t, VDFTypeId::Number> DevkitOverrideAppID{0}; //
VDFKeyPair<k_LastPlayTime, uint32_t, VDFTypeId::Number> LastPlayTime{0}; //
VDFKeyPair<k_FlatpakAppID, std::string, VDFTypeId::String> FlatpakAppID{""}; //
VDFKeyPair<k_tags, std::vector<ShortcutTag>, VDFTypeId::StringList> tags{};
Shortcut& operator=(const Shortcut& other)
{
idx = other.idx;
appId = other.appId;
appName = other.appName;
exe = other.exe;
StartDir = other.StartDir;
icon = other.icon;
ShortcutPath = other.ShortcutPath;
LaunchOptions = other.LaunchOptions;
LaunchOptions = other.LaunchOptions;
IsHidden = other.IsHidden;
AllowDesktopConfig = other.AllowDesktopConfig;
AllowOverlay = other.AllowOverlay;
openvr = other.openvr;
Devkit = other.Devkit;
DevkitGameID = other.DevkitGameID;
DevkitOverrideAppID = other.DevkitOverrideAppID;
LastPlayTime = other.LastPlayTime;
FlatpakAppID = other.FlatpakAppID;
tags = other.tags;
return *this;
}
//std::wstring to_json()
//{
// std::wstring res = L"{";
// res += L"idx: " + std::to_wstring(idx.operator int()) + L",\n";
// res += L"appId: " + std::to_wstring(appId.value) + L",\n";
// res += L"appName: " + std::filesystem::path(appName.value).wstring() + L",\n";
// res += L"StartDir: " + std::filesystem::path(StartDir.value).wstring() + L",\n";
// res += L"ShortcutPath: " + std::filesystem::path(ShortcutPath.value).wstring() + L",\n";
// res += L"LaunchOptions: " + std::filesystem::path(LaunchOptions.value).wstring() + L",\n";
// res += L"IsHidden: " + (IsHidden.value ? L"true" : L"false") + L",\n";
// res += L"AllowDesktopConfig: " + (AllowDesktopConfig.value ? L"true" : L"false") + L",\n";
// res += L"idx: " + std::to_wstring(appId.value) + L",\n";
// res += L"}";
// return res;
//}
};
struct VDFFile {
VDFFile(){};
VDFFile(const VDFFile& other)
{
shortcuts = other.shortcuts;
};
VDFFile(VDFFile&& other)
{
shortcuts = std::move(other.shortcuts);
};
const uint8_t first_byte = 0x00;
const std::string identifier = "shortcuts";
std::vector<Shortcut> shortcuts;
const uint16_t end_marker = 0x0808;
VDFFile& operator=(const VDFFile& other)
{
shortcuts = other.shortcuts;
return *this;
}
VDFFile& operator=(VDFFile&& other)
{
shortcuts = std::move(other.shortcuts);
return *this;
}
//std::wstring to_json()
//{
// std::wstring res = L"[";
// res += L"]";
// return res;
//}
};
class Parser {
private:
static inline std::ifstream ifile;
static inline std::ofstream ofile;
template <typename typ, typename size>
static inline auto readVDFBuffer(typ* buff, size sz)
{
if (ifile.eof()) {
return;
}
ifile.read((char*)buff, sz);
}
template <typename typ>
static inline auto readVDFValue()
{
uint8_t buff[sizeof(typ)];
ifile.read((char*)buff, sizeof(typ));
return *reinterpret_cast<typ*>(buff);
}
static inline std::string readVDFString()
{
std::string str;
char ch = '\x0';
do {
if (ifile.eof()) {
return str;
}
ifile.read(&ch, sizeof(char));
if (ch != '\x0')
str.push_back(ch);
} while (ch != '\x0');
return str;
}
public:
static inline uint32_t calculateAppId(const Shortcut& shortcut)
{
std::string buff = shortcut.exe.value + shortcut.appName.value;
auto checksum = crc::calculate_crc(buff);
return checksum | 0x80000000;
}
static inline VDFFile parseShortcuts(std::filesystem::path path)
{
VDFFile vdffile;
ifile.open(path, std::ios::binary | std::ios::in);
if (!ifile.is_open()) {
return {};
}
auto firsty = readVDFValue<uint8_t>();
if (vdffile.first_byte != firsty) {
// TODO: invalid
ifile.close();
throw std::exception("First byte is invalid in vdf");
}
auto headername = readVDFString();
if (vdffile.identifier != headername) {
// TODO: invalid
ifile.close();
throw std::exception("VDF header is invalid");
}
while (true) {
std::vector<char> buff;
if (ifile.eof()) {
break;
}
char b = '\x0';
readVDFBuffer(&b, 1); // skip 0 byte
Shortcut shortcut;
shortcut.idx.data = readVDFString();
if (shortcut.idx.data == "\x08\x08") {
break;
}
while (true) // TODO;
{
if (ifile.eof()) {
break;
}
const auto tid = static_cast<VDFTypeId>(readVDFValue<uint8_t>());
if (tid == 0x08) {
auto nextbyte = readVDFValue<uint8_t>();
if (nextbyte == 0x08) {
break;
}
else {
// WTF?!
// TODO:
throw std::exception("VDF: WTF");
}
}
auto key = readVDFString();
if ((tid == 0x08 && key[0] == 0x08) || key == "\x08\x08") {
break;
}
if (key == shortcut.appId.key) {
shortcut.appId.value = readVDFValue<uint32_t>();
continue;
}
if (key == shortcut.appName.key) {
shortcut.appName.value = readVDFString();
continue;
}
if (key == shortcut.exe.key) {
shortcut.exe.value = readVDFString();
continue;
}
if (key == shortcut.StartDir.key) {
shortcut.StartDir.value = readVDFString();
continue;
}
if (key == shortcut.icon.key) {
shortcut.icon.value = readVDFString();
continue;
}
if (key == shortcut.ShortcutPath.key) {
shortcut.ShortcutPath.value = readVDFString();
continue;
}
if (key == shortcut.LaunchOptions.key) {
shortcut.LaunchOptions.value = readVDFString();
continue;
}
if (key == shortcut.IsHidden.key) {
shortcut.IsHidden.value = readVDFValue<uint32_t>();
continue;
}
if (key == shortcut.AllowDesktopConfig.key) {
shortcut.AllowDesktopConfig.value = readVDFValue<uint32_t>();
continue;
}
if (key == shortcut.AllowOverlay.key) {
shortcut.AllowOverlay.value = readVDFValue<uint32_t>();
continue;
}
if (key == shortcut.openvr.key) {
shortcut.openvr.value = readVDFValue<uint32_t>();
continue;
}
if (key == shortcut.Devkit.key) {
shortcut.Devkit.value = readVDFValue<uint32_t>();
continue;
}
if (key == shortcut.DevkitGameID.key) {
shortcut.DevkitGameID.value = readVDFString();
continue;
}
if (key == shortcut.DevkitOverrideAppID.key) {
shortcut.DevkitOverrideAppID.value = readVDFValue<uint32_t>();
continue;
}
if (key == shortcut.LastPlayTime.key) {
shortcut.LastPlayTime.value = readVDFValue<uint32_t>();
continue;
}
if (key == shortcut.FlatpakAppID.key) {
shortcut.FlatpakAppID.value = readVDFString();
continue;
}
if (key == shortcut.tags.key) {
ShortcutTag tag;
while (true) {
if (ifile.eof()) {
break;
}
char tbuff[2];
readVDFBuffer(tbuff, 2); // 2 bytes POSSIBLE end marker
ifile.seekg(-1, std::ios_base::cur); // go one back, skip typeId
if (tbuff[0] == 0x08 && tbuff[1] == 0x08) {
ifile.seekg(-1, std::ios_base::cur); // another back
break;
}
tag.idx.data = readVDFString();
if (tag.idx.data == "\x08\x08") {
ifile.seekg(-2, std::ios_base::cur);
break;
}
tag.value = readVDFString();
shortcut.tags.value.push_back(tag);
}
continue;
}
}
if (!(shortcut.idx.data == "\x00\x00")) {
vdffile.shortcuts.push_back(shortcut);
}
}
ifile.close();
return vdffile;
}
static inline bool writeShortcuts(std::filesystem::path path, const VDFFile& vdffile)
{
const auto backupFileName = path.wstring() + L".bak";
if (std::filesystem::exists(path) && !std::filesystem::exists(backupFileName)) {
qDebug() << "No shortcuts backup detected... Creating now...";
const auto copied = std::filesystem::copy_file(path, backupFileName, std::filesystem::copy_options::update_existing);
if (!copied) {
qDebug() << "failed to copy shortcuts.vdf to backup!";
}
}
ofile.open(path.wstring(), std::ios::binary | std::ios::out);
if (!ofile.is_open()) {
return false;
}
ofile.write((char*)&vdffile.first_byte, 1);
ofile.write(vdffile.identifier.data(), vdffile.identifier.length());
ofile.write("\x00", 1);
for (auto& shortcut : vdffile.shortcuts) {
ofile.write("\x00", 1);
ofile.write(shortcut.idx.data.data(), shortcut.idx.data.length());
ofile.write("\x00", 1);
//
ofile.write((char*)&shortcut.appId.type_id, 1);
ofile.write(shortcut.appId.key, 6);
ofile.write((char*)&shortcut.appId.value, 4);
//
ofile.write((char*)&shortcut.appName.type_id, 1);
ofile.write(shortcut.appName.key, 8);
ofile.write(shortcut.appName.value.data(), shortcut.appName.value.length());
ofile.write("\x00", 1);
//
ofile.write((char*)&shortcut.exe.type_id, 1);
ofile.write(shortcut.exe.key, 4);
ofile.write(shortcut.exe.value.data(), shortcut.exe.value.length());
ofile.write("\x00", 1);
//
ofile.write((char*)&shortcut.StartDir.type_id, 1);
ofile.write(shortcut.StartDir.key, 9);
ofile.write(shortcut.StartDir.value.data(), shortcut.StartDir.value.length());
ofile.write("\x00", 1);
//
ofile.write((char*)&shortcut.icon.type_id, 1);
ofile.write(shortcut.icon.key, 5);
ofile.write(shortcut.icon.value.data(), shortcut.icon.value.length());
ofile.write("\x00", 1);
//
ofile.write((char*)&shortcut.ShortcutPath.type_id, 1);
ofile.write(shortcut.ShortcutPath.key, 13);
ofile.write(shortcut.ShortcutPath.value.data(), shortcut.ShortcutPath.value.length());
ofile.write("\x00", 1);
//
ofile.write((char*)&shortcut.LaunchOptions.type_id, 1);
ofile.write(shortcut.LaunchOptions.key, 14);
ofile.write(shortcut.LaunchOptions.value.data(), shortcut.LaunchOptions.value.length());
ofile.write("\x00", 1);
//
ofile.write((char*)&shortcut.IsHidden.type_id, 1);
ofile.write(shortcut.IsHidden.key, 9);
ofile.write((char*)&shortcut.IsHidden.value, 4);
//
ofile.write((char*)&shortcut.AllowDesktopConfig.type_id, 1);
ofile.write(shortcut.AllowDesktopConfig.key, 19);
ofile.write((char*)&shortcut.AllowDesktopConfig.value, 4);
//
ofile.write((char*)&shortcut.AllowOverlay.type_id, 1);
ofile.write(shortcut.AllowOverlay.key, 13);
ofile.write((char*)&shortcut.AllowOverlay.value, 4);
//
ofile.write((char*)&shortcut.openvr.type_id, 1);
ofile.write(shortcut.openvr.key, 7);
ofile.write((char*)&shortcut.openvr.value, 4);
//
ofile.write((char*)&shortcut.Devkit.type_id, 1);
ofile.write(shortcut.Devkit.key, 7);
ofile.write((char*)&shortcut.Devkit.value, 4);
//
ofile.write((char*)&shortcut.DevkitGameID.type_id, 1);
ofile.write(shortcut.DevkitGameID.key, 13);
ofile.write(shortcut.DevkitGameID.value.data(), shortcut.DevkitGameID.value.length());
ofile.write("\x00", 1);
//
ofile.write((char*)&shortcut.DevkitOverrideAppID.type_id, 1);
ofile.write(shortcut.DevkitOverrideAppID.key, 20);
ofile.write((char*)&shortcut.DevkitOverrideAppID.value, 4);
//
ofile.write((char*)&shortcut.LastPlayTime.type_id, 1);
ofile.write(shortcut.LastPlayTime.key, 13);
ofile.write((char*)&shortcut.LastPlayTime.value, 4);
//
ofile.write((char*)&shortcut.FlatpakAppID.type_id, 1);
ofile.write(shortcut.FlatpakAppID.key, 13);
ofile.write(shortcut.FlatpakAppID.value.data(), shortcut.FlatpakAppID.value.length());
ofile.write("\x00", 1);
//
ofile.write((char*)&shortcut.tags.type_id, 1);
ofile.write(shortcut.tags.key, 5);
for (auto& tag : shortcut.tags.value) {
ofile.write(tag.idx.data.data(), tag.idx.data.length());
ofile.write("\x00", 1);
ofile.write(tag.value.data(), tag.value.length());
ofile.write("\x00", 1);
}
ofile.write("\x08\x08", 2);
}
ofile.write("\x08\x08", 2);
ofile.close();
return true;
}
};
} // namespace VDFParser

1
deps/fifo_map vendored

@ -0,0 +1 @@
Subproject commit d732aaf9a315415ae8fd7eb11e3a4c1f80e42a48
Loading…
Cancel
Save