Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmake/compile_definitions/windows.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-misleading-indentation)
# can remove after https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120495 is available in mingw-w64
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-template-body)

# Template-heavy TUs (e.g. confighttp.cpp) exceed the 32767-section COFF limit;
# without big-obj the assembler writes a corrupt symbol table whose COMDAT
# symbols (typeinfo, inline members) resolve as undefined at link time.
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wa,-mbig-obj)

# see gcc bug 98723
add_definitions(-DUSE_BOOST_REGEX)

Expand Down
267 changes: 208 additions & 59 deletions src/confighttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1238,32 +1238,193 @@ namespace confighttp {
return tokens;
}

std::string
trim_vdd_token(std::string value) {
boost::algorithm::trim(value);
if (value.size() >= 2 &&
((value.front() == '"' && value.back() == '"') ||
(value.front() == '\'' && value.back() == '\''))) {
value = value.substr(1, value.size() - 2);
boost::algorithm::trim(value);
}
return value;
}

std::vector<std::string>
parse_vdd_array(std::string value) {
boost::algorithm::trim(value);
if (value.size() >= 2 && value.front() == '[' && value.back() == ']') {
value = value.substr(1, value.size() - 2);
}

std::vector<std::string> result;
for (auto token : split(value, ',')) {
token = trim_vdd_token(token);
if (!token.empty()) {
result.push_back(std::move(token));
}
}
return result;
}

std::vector<VddMode>
normalize_vdd_modes(const std::vector<VddMode> &modes, bool enforce_global_combination_limit = true) {
std::vector<VddMode> normalized;

for (const auto &mode : modes) {
auto resolution = trim_vdd_token(mode.first);
auto refresh_rate = trim_vdd_token(mode.second);
if (resolution.empty() || refresh_rate.empty() || resolution.find('x') == std::string::npos) {
continue;
}

const auto key = resolution + "@" + refresh_rate;
normalized.erase(std::remove_if(normalized.begin(), normalized.end(), [&](const auto &existing_mode) {
return existing_mode.first + "@" + existing_mode.second == key;
}),
normalized.end());
normalized.emplace_back(std::move(resolution), std::move(refresh_rate));
}

while (enforce_global_combination_limit && !normalized.empty()) {
std::set<std::string> resolutions;
std::set<std::string> refresh_rates;
for (const auto &mode : normalized) {
resolutions.insert(mode.first);
refresh_rates.insert(mode.second);
}

const auto combination_limit = vdd_max_mode_combination_count(refresh_rates.size());
if (resolutions.size() * refresh_rates.size() <= combination_limit) {
break;
}
normalized.erase(normalized.begin());
}
return normalized;
}
Comment on lines +1270 to +1304

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

在 HTTPS 请求线程上执行的 O(n²·log n) 归一化,输入规模不受限。

两处都是二次复杂度:

  • 第 1282-1285 行的去重对每个元素做一次全量 remove_if 并拼接字符串,整体 O(n²)。
  • 第 1289-1302 行的裁剪循环每轮重建两个 std::set(O(n log n))却只删除一个元素,整体 O(n²·log n)。

输入 n = |resolutions| × |refresh_rates| 来自 saveVddSettings,而 saveConfig(第 1524-1539 行)只校验了 sunshine_nameadapter_name 的长度,对 resolutions/fps 数组没有任何数量限制。提交 500×500 的数组即可让请求线程长时间挂死;server.config.thread_pool_size = 2(第 3405 行),两个这样的请求就能拖垮整个 Web UI。

建议在 parse_vdd_array 处加数量上限,并把去重改用哈希集合:

🔧 建议修复
   std::vector<VddMode>
   normalize_vdd_modes(const std::vector<VddMode> &modes, bool enforce_global_combination_limit = true) {
+    // 防止请求线程被超大输入拖死
+    constexpr std::size_t MAX_INPUT_MODES = 4096;
+    if (modes.size() > MAX_INPUT_MODES) {
+      BOOST_LOG(warning) << "VDD 模式数量超过上限,拒绝处理: " << modes.size();
+      return {};
+    }
+
     std::vector<VddMode> normalized;
+    std::unordered_map<std::string, std::size_t> index_by_key;
 
     for (const auto &mode : modes) {
       auto resolution = trim_vdd_token(mode.first);
       auto refresh_rate = trim_vdd_token(mode.second);
       if (resolution.empty() || refresh_rate.empty() || resolution.find('x') == std::string::npos) {
         continue;
       }
 
       const auto key = resolution + "@" + refresh_rate;
-      normalized.erase(std::remove_if(normalized.begin(), normalized.end(), [&](const auto &existing_mode) {
-                         return existing_mode.first + "@" + existing_mode.second == key;
-                       }),
-        normalized.end());
-      normalized.emplace_back(std::move(resolution), std::move(refresh_rate));
+      if (auto it = index_by_key.find(key); it != index_by_key.end()) {
+        continue;  // 已存在,保留首次出现的顺序
+      }
+      index_by_key.emplace(key, normalized.size());
+      normalized.emplace_back(std::move(resolution), std::move(refresh_rate));
     }

裁剪循环也建议改为先算出需要删除的数量后一次性 erase(begin(), begin() + count)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/confighttp.cpp` around lines 1270 - 1304, Limit the sizes of the
resolutions and fps arrays in parse_vdd_array before saveVddSettings can pass
unbounded input into normalize_vdd_modes, using a clear validation failure for
oversized requests. In normalize_vdd_modes, replace repeated linear
remove_if/string concatenation deduplication with hash-based key tracking, while
preserving last-occurrence ordering. Compute the number of modes to discard once
and erase that prefix in a single operation instead of rebuilding sets for each
deletion.


bool
saveVddSettings(std::string resArray, std::string fpsArray, std::string gpu_name) {
pt::ptree iddOptionTree;
pt::ptree global_node;
pt::ptree resolutions_nodes;
append_vdd_resolution_node(pt::ptree &resolutions_nodes, const std::string &resolution, const std::string *refresh_rate = nullptr) {
const auto index = resolution.find('x');
if (index == std::string::npos) {
return false;
}

// prepare resolutions setting for vdd
boost::regex pattern("\\[|\\]|\\s+");
char delimiter = ',';
auto width = resolution.substr(0, index);
auto height = resolution.substr(index + 1);
boost::algorithm::trim(width);
boost::algorithm::trim(height);

// 添加全局刷新率到global节点
for (const auto &fps : split(boost::regex_replace(fpsArray, pattern, ""), delimiter)) {
global_node.add("g_refresh_rate", fps);
if (width.empty() || height.empty()) {
return false;
}

std::string str = boost::regex_replace(resArray, pattern, "");
boost::algorithm::trim(str);
for (const auto &resolution : split(str, delimiter)) {
auto index = resolution.find('x');
if(index == std::string::npos) {
pt::ptree res_node;
res_node.put("width", width);
res_node.put("height", height);
if (refresh_rate) {
auto refresh_rate_value = trim_vdd_token(*refresh_rate);
if (refresh_rate_value.empty()) {
return false;
}
res_node.put("refresh_rate", refresh_rate_value);
}
resolutions_nodes.push_back(std::make_pair("resolution"s, res_node));
return true;
}
Comment on lines 1306 to +1334

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

分辨率分隔符只接受小写 x,与读取侧的大小写容忍度不一致。

这里(第 1308 行)和 normalize_vdd_modes(第 1277 行)都只查找小写 'x',而 vdd_utils.cppparse_vdd_resolution(第 437 行)显式接受 'x''X'

结果是形如 1920X1080 的条目会在 normalize_vdd_modes 阶段被静默丢弃,用户既看不到错误提示也不知道配置没生效。

建议统一为大小写不敏感,或至少在丢弃时记日志:

🔧 建议修复
   bool
   append_vdd_resolution_node(pt::ptree &resolutions_nodes, const std::string &resolution, const std::string *refresh_rate = nullptr) {
-    const auto index = resolution.find('x');
+    const auto index = resolution.find_first_of("xX");
     if (index == std::string::npos) {
       return false;
     }

normalize_vdd_modes 第 1277 行的 resolution.find('x') 同样需要改为 find_first_of("xX")

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bool
saveVddSettings(std::string resArray, std::string fpsArray, std::string gpu_name) {
pt::ptree iddOptionTree;
pt::ptree global_node;
pt::ptree resolutions_nodes;
append_vdd_resolution_node(pt::ptree &resolutions_nodes, const std::string &resolution, const std::string *refresh_rate = nullptr) {
const auto index = resolution.find('x');
if (index == std::string::npos) {
return false;
}
// prepare resolutions setting for vdd
boost::regex pattern("\\[|\\]|\\s+");
char delimiter = ',';
auto width = resolution.substr(0, index);
auto height = resolution.substr(index + 1);
boost::algorithm::trim(width);
boost::algorithm::trim(height);
// 添加全局刷新率到global节点
for (const auto &fps : split(boost::regex_replace(fpsArray, pattern, ""), delimiter)) {
global_node.add("g_refresh_rate", fps);
if (width.empty() || height.empty()) {
return false;
}
std::string str = boost::regex_replace(resArray, pattern, "");
boost::algorithm::trim(str);
for (const auto &resolution : split(str, delimiter)) {
auto index = resolution.find('x');
if(index == std::string::npos) {
pt::ptree res_node;
res_node.put("width", width);
res_node.put("height", height);
if (refresh_rate) {
auto refresh_rate_value = trim_vdd_token(*refresh_rate);
if (refresh_rate_value.empty()) {
return false;
}
res_node.put("refresh_rate", refresh_rate_value);
}
resolutions_nodes.push_back(std::make_pair("resolution"s, res_node));
return true;
}
bool
append_vdd_resolution_node(pt::ptree &resolutions_nodes, const std::string &resolution, const std::string *refresh_rate = nullptr) {
const auto index = resolution.find_first_of("xX");
if (index == std::string::npos) {
return false;
}
auto width = resolution.substr(0, index);
auto height = resolution.substr(index + 1);
boost::algorithm::trim(width);
boost::algorithm::trim(height);
if (width.empty() || height.empty()) {
return false;
}
pt::ptree res_node;
res_node.put("width", width);
res_node.put("height", height);
if (refresh_rate) {
auto refresh_rate_value = trim_vdd_token(*refresh_rate);
if (refresh_rate_value.empty()) {
return false;
}
res_node.put("refresh_rate", refresh_rate_value);
}
resolutions_nodes.push_back(std::make_pair("resolution"s, res_node));
return true;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/confighttp.cpp` around lines 1306 - 1334, 统一分辨率解析的分隔符处理:更新
append_vdd_resolution_node 和 normalize_vdd_modes,使查找分隔符同时接受大小写形式的 x,确保类似
1920X1080 的配置不会被静默丢弃,并保持现有无效格式返回失败的行为。


void
append_vdd_global_refresh_rates(pt::ptree &global_node, const std::vector<std::string> &refresh_rates) {
for (const auto &refresh_rate : refresh_rates) {
global_node.add("g_refresh_rate", refresh_rate);
}
}

void
collect_vdd_mode_lists(const std::vector<VddMode> &modes,
std::vector<std::string> &resolutions,
std::vector<std::string> &refresh_rates) {
std::set<std::string> seen_resolutions;
std::set<std::string> seen_refresh_rates;

for (const auto &mode : modes) {
if (seen_resolutions.insert(mode.first).second) {
resolutions.push_back(mode.first);
}
if (seen_refresh_rates.insert(mode.second).second) {
refresh_rates.push_back(mode.second);
}
}
}

void
trim_vdd_temporary_modes(const std::vector<VddMode> &global_modes, std::vector<VddMode> &temporary_modes) {
std::set<std::string> global_resolutions;
std::set<std::string> global_refresh_rates;
for (const auto &mode : global_modes) {
global_resolutions.insert(mode.first);
global_refresh_rates.insert(mode.second);
}

temporary_modes.erase(std::remove_if(temporary_modes.begin(), temporary_modes.end(), [&](const auto &mode) {
return global_resolutions.count(mode.first) != 0 &&
global_refresh_rates.count(mode.second) != 0;
}),
temporary_modes.end());

const auto temporary_mode_limit = std::min(
VDD_MAX_CACHED_TEMPORARY_MODES,
vdd_max_temporary_mode_count(
global_resolutions.size(),
global_refresh_rates.size()));
while (temporary_modes.size() > temporary_mode_limit) {
temporary_modes.erase(temporary_modes.begin());
}
}

void
put_vdd_common_nodes(pt::ptree &iddOptionTree, const std::string &gpu_name) {
pt::ptree monitor_node;
monitor_node.put("count", 1);

pt::ptree gpu_node;
gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name);

iddOptionTree.put_child("monitors", monitor_node);
iddOptionTree.put_child("gpu", gpu_node);
}
Comment on lines +1385 to +1395

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

put_vdd_common_nodes 无条件覆盖整个 monitors 节点,会丢弃已有的其他子项。

put_child("monitors", monitor_node) 用一个只含 count 的新节点整体替换原有 monitors 子树。如果 vdd_settings.xml 中的 monitors 还包含 count 之外的字段(由驱动或用户手工添加),这些字段在每次保存后都会被静默丢弃。

gpu 节点同理——只保留 friendlyname

建议改为在已读取的既有节点上按字段更新,而不是替换整个子树:

🔧 建议修复
   void
   put_vdd_common_nodes(pt::ptree &iddOptionTree, const std::string &gpu_name) {
-    pt::ptree monitor_node;
-    monitor_node.put("count", 1);
-
-    pt::ptree gpu_node;
-    gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name);
-
-    iddOptionTree.put_child("monitors", monitor_node);
-    iddOptionTree.put_child("gpu", gpu_node);
+    // 只更新自己负责的字段,保留同级的其他既有配置
+    iddOptionTree.put("monitors.count", 1);
+    iddOptionTree.put("gpu.friendlyname", gpu_name.empty() ? "default" : gpu_name);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void
put_vdd_common_nodes(pt::ptree &iddOptionTree, const std::string &gpu_name) {
pt::ptree monitor_node;
monitor_node.put("count", 1);
pt::ptree gpu_node;
gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name);
iddOptionTree.put_child("monitors", monitor_node);
iddOptionTree.put_child("gpu", gpu_node);
}
void
put_vdd_common_nodes(pt::ptree &iddOptionTree, const std::string &gpu_name) {
iddOptionTree.put("monitors.count", 1);
iddOptionTree.put("gpu.friendlyname", gpu_name.empty() ? "default" : gpu_name);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/confighttp.cpp` around lines 1385 - 1395, Update put_vdd_common_nodes to
modify existing “monitors” and “gpu” child nodes in iddOptionTree rather than
replacing them with new trees. Preserve all unrelated existing fields while
updating only “monitors.count” and “gpu.friendlyname”, creating the child nodes
only when they do not already exist.


bool
saveVddModeSettings(const std::vector<VddMode> &modes, std::string gpu_name) {
return saveVddModeSettings(modes, {}, std::move(gpu_name));
Comment on lines +1398 to +1399

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve session cache on config saves

The legacy saveVddSettings() path is still called by saveConfig() on every Web UI config save, including saves unrelated to VDD modes, but this overload now rewrites vdd_settings.xml with an explicitly empty temporary list. Because saveVddModeSettings() replaces the whole <resolutions> node, any existing session-mode cache is dropped as soon as the user saves settings. Please merge the existing temporary cache here, pruning only entries covered by the new global mode list.

Useful? React with 👍 / 👎.

}

bool
saveVddModeSettings(const std::vector<VddMode> &global_modes, const std::vector<VddMode> &temporary_modes, std::string gpu_name) {
auto normalized_global_modes = normalize_vdd_modes(global_modes);
if (normalized_global_modes.empty()) {
return false;
}
auto normalized_temporary_modes = normalize_vdd_modes(temporary_modes, false);
trim_vdd_temporary_modes(normalized_global_modes, normalized_temporary_modes);

std::vector<std::string> resolutions;
std::vector<std::string> refresh_rates;
collect_vdd_mode_lists(normalized_global_modes, resolutions, refresh_rates);

pt::ptree global_node;
append_vdd_global_refresh_rates(global_node, refresh_rates);

pt::ptree resolutions_nodes;
for (const auto &resolution : resolutions) {
if (!append_vdd_resolution_node(resolutions_nodes, resolution)) {
return false;
}
}
for (const auto &mode : normalized_temporary_modes) {
if (!append_vdd_resolution_node(resolutions_nodes, mode.first, &mode.second)) {
return false;
}
pt::ptree res_node;
res_node.put("width", resolution.substr(0, index));
res_node.put("height", resolution.substr(index + 1));
resolutions_nodes.push_back(std::make_pair("resolution"s, res_node));
}

// 类似于 config.cpp 中的 path_f 函数逻辑,使用相对路径
Expand All @@ -1272,60 +1433,29 @@ namespace confighttp {
BOOST_LOG(info) << "VDD配置文件路径: " << idd_option_path.string();

if (!fs::exists(idd_option_path)) {
return false;
return false;
}

// 先读取现有配置文件
pt::ptree iddOptionTree;
pt::ptree existing_root;
pt::ptree root;

try {
pt::read_xml(idd_option_path.string(), existing_root);
// 如果现有配置文件中已有vdd_settings节点
if (existing_root.get_child_optional("vdd_settings")) {
// 复制现有配置
iddOptionTree = existing_root.get_child("vdd_settings");

// 更新需要更改的部分
pt::ptree monitor_node;
monitor_node.put("count", 1);

pt::ptree gpu_node;
gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name);

// 替换配置
iddOptionTree.put_child("monitors", monitor_node);
iddOptionTree.put_child("gpu", gpu_node);
iddOptionTree.put_child("global", global_node);
iddOptionTree.put_child("resolutions", resolutions_nodes);
} else {
// 如果没有vdd_settings节点,创建新的
pt::ptree monitor_node;
monitor_node.put("count", 1);

pt::ptree gpu_node;
gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name);

iddOptionTree.add_child("monitors", monitor_node);
iddOptionTree.add_child("gpu", gpu_node);
iddOptionTree.add_child("global", global_node);
iddOptionTree.add_child("resolutions", resolutions_nodes);
}
} catch(std::exception &e) {
// 读取失败,创建新的配置
}
catch (std::exception &e) {
BOOST_LOG(warning) << "读取现有VDD配置失败,创建新配置: " << e.what();
}

pt::ptree monitor_node;
monitor_node.put("count", 1);

pt::ptree gpu_node;
gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name);
put_vdd_common_nodes(iddOptionTree, gpu_name);

iddOptionTree.add_child("monitors", monitor_node);
iddOptionTree.add_child("gpu", gpu_node);
iddOptionTree.add_child("global", global_node);
iddOptionTree.add_child("resolutions", resolutions_nodes);
}
iddOptionTree.put_child("global", global_node);
iddOptionTree.put_child("resolutions", resolutions_nodes);
iddOptionTree.erase("sunshine_mode_cache");
Comment on lines 1444 to +1458

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

XML 解析失败时不应继续写入——当前实现会用空树重建文件,静默丢弃其他配置。

第 1450-1452 行捕获异常后只记 warning,iddOptionTree 保持为空,随后第 1454-1458 行照常写入并在第 1460 行整体落盘。结果是 vdd_settings.xml 一旦损坏,本次保存会把它整个重建,丢掉所有本函数不负责的节点——例如 vdd_utils.cpp 第 334 行写入的 vdd_settings.cursor.HardwareCursor

这个风险在本 PR 中被放大:clear_vdd_mode_cache 正是在 VDD 启动失败路径上调用本函数,而"启动失败"与"XML 损坏"高度相关,恰好是最容易踩中覆盖逻辑的场景。

建议解析失败时直接放弃保存,让上层看到失败:

🔧 建议修复
     try {
       pt::read_xml(idd_option_path.string(), existing_root);
       if (existing_root.get_child_optional("vdd_settings")) {
         iddOptionTree = existing_root.get_child("vdd_settings");
       }
     }
     catch (std::exception &e) {
-      BOOST_LOG(warning) << "读取现有VDD配置失败,创建新配置: " << e.what();
+      // 不能用空树重建:那会丢掉 cursor.HardwareCursor 等本函数不负责的节点。
+      BOOST_LOG(error) << "读取现有VDD配置失败,放弃本次保存以免覆盖其他设置: " << e.what();
+      return false;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
pt::read_xml(idd_option_path.string(), existing_root);
// 如果现有配置文件中已有vdd_settings节点
if (existing_root.get_child_optional("vdd_settings")) {
// 复制现有配置
iddOptionTree = existing_root.get_child("vdd_settings");
// 更新需要更改的部分
pt::ptree monitor_node;
monitor_node.put("count", 1);
pt::ptree gpu_node;
gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name);
// 替换配置
iddOptionTree.put_child("monitors", monitor_node);
iddOptionTree.put_child("gpu", gpu_node);
iddOptionTree.put_child("global", global_node);
iddOptionTree.put_child("resolutions", resolutions_nodes);
} else {
// 如果没有vdd_settings节点,创建新的
pt::ptree monitor_node;
monitor_node.put("count", 1);
pt::ptree gpu_node;
gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name);
iddOptionTree.add_child("monitors", monitor_node);
iddOptionTree.add_child("gpu", gpu_node);
iddOptionTree.add_child("global", global_node);
iddOptionTree.add_child("resolutions", resolutions_nodes);
}
} catch(std::exception &e) {
// 读取失败,创建新的配置
}
catch (std::exception &e) {
BOOST_LOG(warning) << "读取现有VDD配置失败,创建新配置: " << e.what();
}
pt::ptree monitor_node;
monitor_node.put("count", 1);
pt::ptree gpu_node;
gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name);
put_vdd_common_nodes(iddOptionTree, gpu_name);
iddOptionTree.add_child("monitors", monitor_node);
iddOptionTree.add_child("gpu", gpu_node);
iddOptionTree.add_child("global", global_node);
iddOptionTree.add_child("resolutions", resolutions_nodes);
}
iddOptionTree.put_child("global", global_node);
iddOptionTree.put_child("resolutions", resolutions_nodes);
iddOptionTree.erase("sunshine_mode_cache");
try {
pt::read_xml(idd_option_path.string(), existing_root);
if (existing_root.get_child_optional("vdd_settings")) {
iddOptionTree = existing_root.get_child("vdd_settings");
}
}
catch (std::exception &e) {
// 不能用空树重建:那会丢掉 cursor.HardwareCursor 等本函数不负责的节点。
BOOST_LOG(error) << "读取现有VDD配置失败,放弃本次保存以免覆盖其他设置: " << e.what();
return false;
}
put_vdd_common_nodes(iddOptionTree, gpu_name);
iddOptionTree.put_child("global", global_node);
iddOptionTree.put_child("resolutions", resolutions_nodes);
iddOptionTree.erase("sunshine_mode_cache");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/confighttp.cpp` around lines 1444 - 1458, 在读取现有配置的 try/catch 流程中,解析 XML
失败后不要继续执行 put_vdd_common_nodes、global/resolutions
更新及后续落盘;应通过该保存函数的现有失败传播机制立即返回失败,让上层感知错误并保留原文件内容。仅在 pt::read_xml 成功时继续使用
iddOptionTree 更新配置。


root.add_child("vdd_settings", iddOptionTree);
try {
Expand All @@ -1351,6 +1481,25 @@ namespace confighttp {
}
}

bool
saveVddSettings(std::string resArray, std::string fpsArray, std::string gpu_name) {
std::vector<VddMode> modes;
const auto resolutions = parse_vdd_array(std::move(resArray));
const auto refresh_rates = parse_vdd_array(std::move(fpsArray));

for (const auto &resolution : resolutions) {
if (resolution.find('x') == std::string::npos) {
return false;
}

for (const auto &refresh_rate : refresh_rates) {
modes.emplace_back(resolution, refresh_rate);
}
}

return saveVddModeSettings(modes, std::move(gpu_name));
}

void
saveConfig(resp_https_t response, req_https_t request) {
if (!check_content_type(response, request, "application/json")) return;
Expand Down
Loading
Loading