mirror of
https://github.com/mirkobrombin/raven.git
synced 2026-11-03 21:46:32 +00:00
563 lines
18 KiB
C++
563 lines
18 KiB
C++
#include "kernel_context.h"
|
|
#include "text.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <sys/stat.h>
|
|
|
|
namespace raven {
|
|
|
|
namespace {
|
|
|
|
constexpr size_t max_path = 260;
|
|
|
|
bool separator(char16_t character)
|
|
{
|
|
return character == u'\\' || character == u'/';
|
|
}
|
|
|
|
// Index of the file name inside a path: after the last separator or drive colon.
|
|
size_t file_name_start(std::u16string_view path)
|
|
{
|
|
size_t start = 0;
|
|
for (size_t at = 0; at < path.size(); ++at)
|
|
if (separator(path[at]) || (path[at] == u':' && at == 1))
|
|
start = at + 1;
|
|
return start;
|
|
}
|
|
|
|
size_t extension_start(std::u16string_view path)
|
|
{
|
|
const size_t name = file_name_start(path);
|
|
size_t dot = std::u16string_view::npos;
|
|
for (size_t at = name; at < path.size(); ++at)
|
|
if (path[at] == u'.')
|
|
dot = at;
|
|
return dot == std::u16string_view::npos ? path.size() : dot;
|
|
}
|
|
|
|
// Length of the root part: "X:\", "\\server\share\" or a single leading separator.
|
|
size_t root_length(std::u16string_view path)
|
|
{
|
|
if (path.size() >= 3 && path[1] == u':' && separator(path[2]))
|
|
return 3;
|
|
if (path.size() >= 2 && separator(path[0]) && separator(path[1])) {
|
|
size_t at = 2, parts = 0;
|
|
while (at < path.size() && parts < 2) {
|
|
while (at < path.size() && !separator(path[at]))
|
|
++at;
|
|
++parts;
|
|
if (at < path.size() && parts < 2)
|
|
++at;
|
|
}
|
|
if (at < path.size())
|
|
++at;
|
|
return at;
|
|
}
|
|
if (!path.empty() && separator(path[0]))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
bool is_unc_root(std::u16string_view path)
|
|
{
|
|
return path.size() >= 2 && separator(path[0]) && separator(path[1]);
|
|
}
|
|
|
|
std::u16string canonical(std::u16string_view path)
|
|
{
|
|
const size_t root = root_length(path);
|
|
std::u16string result(path.substr(0, root));
|
|
for (auto &character : result)
|
|
if (character == u'/')
|
|
character = u'\\';
|
|
std::vector<std::u16string> parts;
|
|
size_t at = root;
|
|
const bool trailing = !path.empty() && separator(path.back()) && path.size() > root;
|
|
while (at <= path.size()) {
|
|
size_t end = at;
|
|
while (end < path.size() && !separator(path[end]))
|
|
++end;
|
|
const auto part = path.substr(at, end - at);
|
|
if (part == u"..") {
|
|
if (!parts.empty())
|
|
parts.pop_back();
|
|
} else if (!part.empty() && part != u".") {
|
|
parts.emplace_back(part);
|
|
}
|
|
if (end >= path.size())
|
|
break;
|
|
at = end + 1;
|
|
}
|
|
for (size_t index = 0; index < parts.size(); ++index) {
|
|
if (!result.empty() && result.back() != u'\\')
|
|
result.push_back(u'\\');
|
|
result += parts[index];
|
|
}
|
|
if (trailing && !parts.empty())
|
|
result.push_back(u'\\');
|
|
if (result.empty())
|
|
result = u"\\";
|
|
return result;
|
|
}
|
|
|
|
std::u16string join(std::u16string_view directory, std::u16string_view file)
|
|
{
|
|
if (directory.empty())
|
|
return std::u16string(file);
|
|
if (file.empty())
|
|
return std::u16string(directory);
|
|
std::u16string result(directory);
|
|
if (!separator(result.back()))
|
|
result.push_back(u'\\');
|
|
result += separator(file.front()) ? file.substr(1) : file;
|
|
return result;
|
|
}
|
|
|
|
bool absolute(std::u16string_view path)
|
|
{
|
|
return root_length(path) > 0;
|
|
}
|
|
|
|
bool match_one(std::u16string_view spec, std::u16string_view name)
|
|
{
|
|
size_t p = 0, n = 0, star = std::u16string_view::npos, mark = 0;
|
|
while (n < name.size()) {
|
|
if (p < spec.size() && (spec[p] == u'?' || equal_fold(spec.substr(p, 1), name.substr(n, 1)))) {
|
|
++p;
|
|
++n;
|
|
} else if (p < spec.size() && spec[p] == u'*') {
|
|
star = p++;
|
|
mark = n;
|
|
} else if (star != std::u16string_view::npos) {
|
|
p = star + 1;
|
|
n = ++mark;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
while (p < spec.size() && spec[p] == u'*')
|
|
++p;
|
|
return p == spec.size();
|
|
}
|
|
|
|
// HLS in the 0..240 range of shlwapi, from 8-bit RGB.
|
|
void rgb_to_hls(uint32_t rgb, uint16_t &hue, uint16_t &luminance, uint16_t &saturation)
|
|
{
|
|
const int r = rgb & 255, g = (rgb >> 8) & 255, b = (rgb >> 16) & 255;
|
|
const int maximum = std::max({r, g, b}), minimum = std::min({r, g, b});
|
|
const int sum = maximum + minimum, difference = maximum - minimum;
|
|
luminance = uint16_t(((sum * 240) + 255) / 510);
|
|
if (!difference) {
|
|
saturation = 0;
|
|
hue = 160;
|
|
return;
|
|
}
|
|
saturation = uint16_t(luminance <= 120 ? (difference * 240 + sum / 2) / sum : (difference * 240 + (510 - sum) / 2) / (510 - sum));
|
|
const int rc = ((maximum - r) * 40 + difference / 2) / difference, gc = ((maximum - g) * 40 + difference / 2) / difference,
|
|
bc = ((maximum - b) * 40 + difference / 2) / difference;
|
|
int result = r == maximum ? bc - gc : g == maximum ? 80 + rc - bc : 160 + gc - rc;
|
|
if (result < 0)
|
|
result += 240;
|
|
if (result > 240)
|
|
result -= 240;
|
|
hue = uint16_t(result);
|
|
}
|
|
|
|
int hue_component(int n1, int n2, int hue)
|
|
{
|
|
if (hue < 0) hue += 240;
|
|
if (hue > 240) hue -= 240;
|
|
if (hue < 40) return n1 + ((n2 - n1) * hue + 20) / 40;
|
|
if (hue < 120) return n2;
|
|
if (hue < 160) return n1 + ((n2 - n1) * (160 - hue) + 20) / 40;
|
|
return n1;
|
|
}
|
|
|
|
uint32_t hls_to_rgb(int hue, int luminance, int saturation)
|
|
{
|
|
int r, g, b;
|
|
if (!saturation) {
|
|
r = g = b = luminance * 255 / 240;
|
|
} else {
|
|
const int m2 = luminance <= 120 ? (luminance * (240 + saturation) + 120) / 240 : luminance + saturation - (luminance * saturation + 120) / 240;
|
|
const int m1 = 2 * luminance - m2;
|
|
const auto scale = [](int value) { return std::clamp((value * 255 + 120) / 240, 0, 255); };
|
|
r = scale(hue_component(m1, m2, hue + 80));
|
|
g = scale(hue_component(m1, m2, hue));
|
|
b = scale(hue_component(m1, m2, hue - 80));
|
|
}
|
|
return uint32_t(r) | uint32_t(g) << 8 | uint32_t(b) << 16;
|
|
}
|
|
|
|
std::u16string compact(std::u16string_view path, size_t capacity)
|
|
{
|
|
if (path.size() < capacity)
|
|
return std::u16string(path);
|
|
if (capacity < 4)
|
|
return std::u16string(path.substr(0, capacity ? capacity - 1 : 0));
|
|
const size_t name = file_name_start(path);
|
|
const auto file = path.substr(name);
|
|
const std::u16string_view ellipsis = u"...";
|
|
if (file.size() + ellipsis.size() + 1 >= capacity) {
|
|
std::u16string result(file.substr(0, capacity - 1 - ellipsis.size()));
|
|
return result + std::u16string(ellipsis);
|
|
}
|
|
const size_t room = capacity - 1 - ellipsis.size() - file.size();
|
|
std::u16string result(path.substr(0, std::min(room, name)));
|
|
return result + std::u16string(ellipsis) + std::u16string(file);
|
|
}
|
|
|
|
}
|
|
|
|
void register_shlwapi_api(ApiRegistry ®istry, KernelContext &context)
|
|
{
|
|
auto &paths = context.paths;
|
|
// Path functions edit a MAX_PATH buffer in place; this reads it, applies the edit and writes it back.
|
|
const auto edit = [](ApiCall &call, const std::function<bool(std::u16string &)> &change) {
|
|
auto text = call.wide(call[0], max_path * 4);
|
|
const bool changed = change(text);
|
|
call.put_wide(call[0], text, max_path * 4);
|
|
call.ret(changed);
|
|
};
|
|
// Completion lists are drawn by the shell; an edit control without one still works, so enabling it succeeds without a list.
|
|
registry.add("SHAutoComplete", 2, [](ApiCall &call) { call.ret(0); });
|
|
registry.add("PathRemoveFileSpecW", 1, [edit](ApiCall &call) {
|
|
edit(call, [](std::u16string &path) {
|
|
const size_t root = root_length(path);
|
|
if (path.empty() || (path.size() == root && root))
|
|
return false;
|
|
const auto last = path.find_last_of(u"\\/");
|
|
if (last == path.npos) {
|
|
if (path.size() > 2 && path[1] == u':') {
|
|
path.resize(2);
|
|
return true;
|
|
}
|
|
path.clear();
|
|
return true;
|
|
}
|
|
if (is_unc_root(path) && last < root) {
|
|
if (last + 1 == path.size() && path.size() > root - 1 && root == path.size())
|
|
return false;
|
|
}
|
|
if (last + 1 == path.size()) {
|
|
path.pop_back();
|
|
return true;
|
|
}
|
|
if (last < root && root)
|
|
path.resize(root);
|
|
else
|
|
path.resize(last);
|
|
return true;
|
|
});
|
|
});
|
|
registry.add("PathStripPathW", 1, [edit](ApiCall &call) {
|
|
edit(call, [](std::u16string &path) {
|
|
path.erase(0, file_name_start(path));
|
|
return true;
|
|
});
|
|
});
|
|
registry.add("PathRemoveExtensionW", 1, [edit](ApiCall &call) {
|
|
edit(call, [](std::u16string &path) {
|
|
path.resize(extension_start(path));
|
|
return true;
|
|
});
|
|
});
|
|
registry.add("PathAddExtensionW", 2, [](ApiCall &call) {
|
|
auto path = call.wide(call[0], max_path * 4);
|
|
const auto extension = call[1] ? call.wide(call[1]) : u".exe";
|
|
if (extension_start(path) != path.size()) {
|
|
call.ret(0);
|
|
return;
|
|
}
|
|
if (path.size() + extension.size() >= max_path) {
|
|
call.ret(0);
|
|
return;
|
|
}
|
|
path += extension;
|
|
call.put_wide(call[0], path, max_path);
|
|
call.ret(1);
|
|
});
|
|
registry.add("PathAppendW", 2, [](ApiCall &call) {
|
|
auto path = call.wide(call[0], max_path * 4);
|
|
const auto more = call.wide(call[1]);
|
|
auto joined = canonical(join(path, more));
|
|
if (joined.size() >= max_path) {
|
|
call.ret(0);
|
|
return;
|
|
}
|
|
call.put_wide(call[0], joined, max_path);
|
|
call.ret(1);
|
|
});
|
|
registry.add("PathCombineW", 3, [](ApiCall &call) {
|
|
const auto directory = call[1] ? call.wide(call[1]) : std::u16string();
|
|
const auto file = call[2] ? call.wide(call[2]) : std::u16string();
|
|
const auto combined = absolute(file) && !(separator(file[0]) && !is_unc_root(file)) ? file : join(directory, file);
|
|
const auto result = canonical(combined);
|
|
if (result.size() >= max_path) {
|
|
call.ret(0);
|
|
return;
|
|
}
|
|
call.put_wide(call[0], result, max_path);
|
|
call.ret(call[0]);
|
|
});
|
|
registry.add("PathCanonicalizeW", 2, [](ApiCall &call) {
|
|
const auto result = canonical(call.wide(call[1]));
|
|
if (result.size() >= max_path) {
|
|
call.fail(206);
|
|
return;
|
|
}
|
|
call.put_wide(call[0], result, max_path);
|
|
call.ret(1);
|
|
});
|
|
registry.add("PathIsRelativeW", 1, [](ApiCall &call) {
|
|
const auto path = call.wide(call[0]);
|
|
call.ret(path.empty() || !(root_length(path) > 0));
|
|
});
|
|
registry.add("PathIsRootW", 1, [](ApiCall &call) {
|
|
const auto path = call.wide(call[0]);
|
|
call.ret(!path.empty() && root_length(path) == path.size());
|
|
});
|
|
registry.add("PathIsUNCW", 1, [](ApiCall &call) { call.ret(is_unc_root(call.wide(call[0]))); });
|
|
registry.add("PathIsNetworkPathW", 1, [](ApiCall &call) { call.ret(is_unc_root(call.wide(call[0]))); });
|
|
registry.add("PathIsFileSpecW", 1, [](ApiCall &call) { call.ret(call.wide(call[0]).find_first_of(u"\\/:") == std::u16string::npos); });
|
|
registry.add("PathGetDriveNumberW", 1, [](ApiCall &call) {
|
|
const auto path = call.wide(call[0]);
|
|
if (path.size() >= 2 && path[1] == u':' && ((path[0] >= u'a' && path[0] <= u'z') || (path[0] >= u'A' && path[0] <= u'Z')))
|
|
call.ret(uint32_t(upper_case(path[0]) - u'A'));
|
|
else
|
|
call.ret(uint32_t(-1));
|
|
});
|
|
registry.add("PathFindFileNameW", 1, [](ApiCall &call) {
|
|
const auto path = call.wide(call[0]);
|
|
call.ret(call[0] + file_name_start(path) * 2);
|
|
});
|
|
registry.add("PathFindExtensionW", 1, [](ApiCall &call) {
|
|
const auto path = call.wide(call[0]);
|
|
call.ret(call[0] + extension_start(path) * 2);
|
|
});
|
|
registry.add("PathSkipRootW", 1, [](ApiCall &call) {
|
|
const auto path = call.wide(call[0]);
|
|
const size_t root = root_length(path);
|
|
call.ret(root ? call[0] + root * 2 : 0);
|
|
});
|
|
registry.add("PathAddBackslashW", 1, [](ApiCall &call) {
|
|
auto path = call.wide(call[0], max_path * 4);
|
|
if (path.empty() || separator(path.back())) {
|
|
call.ret(call[0] + path.size() * 2);
|
|
return;
|
|
}
|
|
if (path.size() + 1 >= max_path) {
|
|
call.ret(0);
|
|
return;
|
|
}
|
|
path.push_back(u'\\');
|
|
call.put_wide(call[0], path, max_path);
|
|
call.ret(call[0] + path.size() * 2);
|
|
});
|
|
registry.add("PathRemoveBackslashW", 1, [](ApiCall &call) {
|
|
auto path = call.wide(call[0], max_path * 4);
|
|
if (path.size() > root_length(path) && !path.empty() && separator(path.back())) {
|
|
path.pop_back();
|
|
call.put_wide(call[0], path, max_path);
|
|
}
|
|
call.ret(call[0] + path.size() * 2);
|
|
});
|
|
registry.add("PathQuoteSpacesW", 1, [](ApiCall &call) {
|
|
auto path = call.wide(call[0], max_path * 4);
|
|
if (path.find(u' ') != path.npos && path.size() + 2 < max_path) {
|
|
path = u"\"" + path + u"\"";
|
|
call.put_wide(call[0], path, max_path);
|
|
}
|
|
});
|
|
registry.add("PathUnquoteSpacesW", 1, [](ApiCall &call) {
|
|
auto path = call.wide(call[0], max_path * 4);
|
|
if (path.size() >= 2 && path.front() == u'"' && path.back() == u'"') {
|
|
path = path.substr(1, path.size() - 2);
|
|
call.put_wide(call[0], path, max_path);
|
|
}
|
|
});
|
|
registry.add("PathMatchSpecW", 2, [](ApiCall &call) {
|
|
const auto file = call.wide(call[0]);
|
|
const auto spec = call.wide(call[1]);
|
|
bool matched = false;
|
|
size_t at = 0;
|
|
while (at <= spec.size() && !matched) {
|
|
const size_t end = std::min(spec.find(u';', at), spec.size());
|
|
auto part = std::u16string_view(spec).substr(at, end - at);
|
|
while (!part.empty() && part.front() == u' ')
|
|
part.remove_prefix(1);
|
|
matched = !part.empty() && match_one(part, file);
|
|
at = end + 1;
|
|
}
|
|
call.ret(matched);
|
|
});
|
|
registry.add("PathCompactPathExW", 4, [](ApiCall &call) {
|
|
const auto path = call.wide(call[1]);
|
|
const size_t capacity = call.u32(2);
|
|
if (!capacity) {
|
|
call.ret(0);
|
|
return;
|
|
}
|
|
const auto result = compact(path, capacity);
|
|
call.put_wide(call[0], result, capacity);
|
|
call.ret(1);
|
|
});
|
|
registry.add("PathFileExistsW", 1, [&](ApiCall &call) {
|
|
const auto resolved = paths.resolve(call.wide(call[0]));
|
|
call.ret(resolved && resolved->exists);
|
|
});
|
|
registry.add("PathIsDirectoryW", 1, [&](ApiCall &call) {
|
|
const auto resolved = paths.resolve(call.wide(call[0]));
|
|
struct stat metadata{};
|
|
call.ret(resolved && resolved->exists && !::stat(resolved->host.c_str(), &metadata) && S_ISDIR(metadata.st_mode) ? 0x10 : 0);
|
|
});
|
|
registry.add("PathIsDirectoryEmptyW", 1, [&](ApiCall &call) {
|
|
const auto resolved = paths.resolve(call.wide(call[0]));
|
|
std::error_code error;
|
|
call.ret(resolved && std::filesystem::is_directory(resolved->host, error) && std::filesystem::is_empty(resolved->host, error));
|
|
});
|
|
|
|
registry.add("StrCmpW", 2, [](ApiCall &call) {
|
|
const auto a = call.wide(call[0]), b = call.wide(call[1]);
|
|
call.ret(uint32_t(a < b ? -1 : a > b ? 1 : 0));
|
|
});
|
|
registry.add("StrCmpIW", 2, [](ApiCall &call) { call.ret(uint32_t(compare_fold(call.wide(call[0]), call.wide(call[1])))); });
|
|
registry.add("StrCmpNW", 3, [](ApiCall &call) {
|
|
const size_t count = call.u32(2);
|
|
const auto a = call.wide(call[0]).substr(0, count), b = call.wide(call[1]).substr(0, count);
|
|
call.ret(uint32_t(a < b ? -1 : a > b ? 1 : 0));
|
|
});
|
|
registry.add("StrCmpNIW", 3, [](ApiCall &call) {
|
|
const size_t count = call.u32(2);
|
|
call.ret(uint32_t(compare_fold(call.wide(call[0]).substr(0, count), call.wide(call[1]).substr(0, count))));
|
|
});
|
|
registry.add("StrStrW", 2, [](ApiCall &call) {
|
|
const auto text = call.wide(call[0]), needle = call.wide(call[1]);
|
|
const auto found = text.find(needle);
|
|
call.ret(found == text.npos ? 0 : call[0] + found * 2);
|
|
});
|
|
registry.add("StrStrIW", 2, [](ApiCall &call) {
|
|
const auto text = call.wide(call[0]), needle = call.wide(call[1]);
|
|
for (size_t at = 0; at + needle.size() <= text.size(); ++at)
|
|
if (equal_fold(std::u16string_view(text).substr(at, needle.size()), needle)) {
|
|
call.ret(call[0] + at * 2);
|
|
return;
|
|
}
|
|
call.ret(0);
|
|
});
|
|
registry.add("StrChrW", 2, [](ApiCall &call) {
|
|
const auto text = call.wide(call[0]);
|
|
const auto found = text.find(char16_t(call[1]));
|
|
call.ret(found == text.npos ? 0 : call[0] + found * 2);
|
|
});
|
|
registry.add("StrRChrW", 3, [](ApiCall &call) {
|
|
const auto text = call.wide(call[0]);
|
|
const auto end = call[1] ? std::min<size_t>((call[1] - call[0]) / 2, text.size()) : text.size();
|
|
const auto found = text.substr(0, end).rfind(char16_t(call[2]));
|
|
call.ret(found == text.npos ? 0 : call[0] + found * 2);
|
|
});
|
|
registry.add("StrToIntW", 1, [](ApiCall &call) {
|
|
const auto text = call.wide(call[0]);
|
|
size_t at = 0;
|
|
bool negative = false;
|
|
if (at < text.size() && text[at] == u'-') {
|
|
negative = true;
|
|
++at;
|
|
}
|
|
int64_t value = 0;
|
|
for (; at < text.size() && text[at] >= u'0' && text[at] <= u'9'; ++at)
|
|
value = std::min<int64_t>(value * 10 + (text[at] - u'0'), 0x7fffffffLL + 1);
|
|
call.ret(uint32_t(int32_t(negative ? -value : value)));
|
|
});
|
|
registry.add("StrTrimW", 2, [](ApiCall &call) {
|
|
auto text = call.wide(call[0]);
|
|
const auto trim = call.wide(call[1]);
|
|
const auto first = text.find_first_not_of(trim);
|
|
const bool changed = first != 0 || (!text.empty() && trim.find(text.back()) != trim.npos);
|
|
if (first == text.npos) text.clear();
|
|
else text = text.substr(first, text.find_last_not_of(trim) - first + 1);
|
|
call.put_wide(call[0], text, text.size() + 1);
|
|
call.ret(changed);
|
|
});
|
|
registry.add("StrCpyNW", 3, [](ApiCall &call) {
|
|
const int32_t capacity = call.i32(2);
|
|
const auto text = call.wide(call[1]);
|
|
if (capacity > 0)
|
|
call.put_wide(call[0], text.substr(0, size_t(capacity) - 1), size_t(capacity));
|
|
call.ret(call[0]);
|
|
});
|
|
registry.add("StrNCatW", 3, [](ApiCall &call) {
|
|
auto text = call.wide(call[0]);
|
|
const int32_t capacity = call.i32(2);
|
|
const auto more = call.wide(call[1]);
|
|
if (capacity > int32_t(text.size() + 1)) {
|
|
text += more.substr(0, size_t(capacity) - text.size() - 1);
|
|
call.put_wide(call[0], text, text.size() + 1);
|
|
}
|
|
call.ret(call[0]);
|
|
});
|
|
registry.add("StrDupW", 1, [&](ApiCall &call) {
|
|
const auto text = call.wide(call[0]);
|
|
const auto copy = context.allocator.allocate((text.size() + 1) * 2, false);
|
|
if (!copy) {
|
|
call.ret(0);
|
|
return;
|
|
}
|
|
call.put_wide(copy, text, text.size() + 1);
|
|
call.ret(copy);
|
|
});
|
|
registry.add("StrFormatByteSizeW", 3, [](ApiCall &call) {
|
|
const uint64_t value = call[0];
|
|
static constexpr const char *units[] = {" bytes", " KB", " MB", " GB", " TB", " PB", " EB"};
|
|
std::string text;
|
|
if (value < 1024) {
|
|
text = std::to_string(value) + units[0];
|
|
} else {
|
|
double scaled = double(value);
|
|
size_t unit = 0;
|
|
while (scaled >= 1024 && unit < 6) {
|
|
scaled /= 1024;
|
|
++unit;
|
|
}
|
|
// Windows shows three significant digits.
|
|
char buffer[32];
|
|
const int decimals = scaled >= 100 ? 0 : scaled >= 10 ? 1 : 2;
|
|
std::snprintf(buffer, sizeof buffer, "%.*f", decimals, scaled);
|
|
text = std::string(buffer) + units[unit];
|
|
}
|
|
call.put_wide(call[1], widen_ascii(text), call.u32(2));
|
|
call.ret(call[1]);
|
|
});
|
|
|
|
registry.add("ColorRGBToHLS", 4, [](ApiCall &call) {
|
|
uint16_t hue, luminance, saturation;
|
|
rgb_to_hls(call.u32(0), hue, luminance, saturation);
|
|
if (call[1]) call.store<uint16_t>(call[1], hue);
|
|
if (call[2]) call.store<uint16_t>(call[2], luminance);
|
|
if (call[3]) call.store<uint16_t>(call[3], saturation);
|
|
});
|
|
registry.add("ColorHLSToRGB", 3, [](ApiCall &call) {
|
|
call.ret(hls_to_rgb(int(call[0] & 0xffff), int(call[1] & 0xffff), int(call[2] & 0xffff)));
|
|
});
|
|
registry.add("ColorAdjustLuma", 3, [](ApiCall &call) {
|
|
const uint32_t color = call.u32(0);
|
|
const int32_t amount = call.i32(1);
|
|
if (!amount) {
|
|
call.ret(color);
|
|
return;
|
|
}
|
|
uint16_t hue, luminance, saturation;
|
|
rgb_to_hls(color, hue, luminance, saturation);
|
|
int value = luminance;
|
|
if (call.u32(2))
|
|
value = value * (1000 + amount) / 1000;
|
|
else
|
|
value += amount * 240 / 1000;
|
|
call.ret(hls_to_rgb(hue, std::clamp(value, 0, 240), saturation));
|
|
});
|
|
registry.add("AssocQueryStringW", 6, [](ApiCall &call) { call.ret(0x80070483U); });
|
|
}
|
|
|
|
}
|