File indexing completed on 2024-05-12 15:57:04

0001 /*
0002  * SPDX-FileCopyrightText: 2022 Alvin Wong <alvin@alvinhc.com>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 // Get Windows Vista API
0008 #if defined(WINVER) && WINVER < 0x0600
0009 #  undef WINVER
0010 #endif
0011 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0600
0012 #  undef _WIN32_WINNT
0013 #endif
0014 #ifndef WINVER
0015 #  define WINVER 0x0600
0016 #endif
0017 #ifndef _WIN32_WINNT
0018 #  define _WIN32_WINNT 0x0600
0019 #endif
0020 
0021 #include "KisWindowsPackageUtils.h"
0022 
0023 #include <windows.h>
0024 #include <Shlobj.h>
0025 
0026 #include <QDebug>
0027 #include <QString>
0028 
0029 
0030 constexpr int appmodel_PACKAGE_FULL_NAME_MAX_LENGTH = 127;
0031 
0032 constexpr LONG winerror_APPMODEL_ERROR_NO_PACKAGE = 15700;
0033 
0034 // ---
0035 // GetCurrentPackageFamilyName
0036 // appmodel.h / Kernel32.dll / Windows 8
0037 // ---
0038 typedef LONG (WINAPI *pGetCurrentPackageFamilyName_t)(
0039     UINT32 *packageFamilyNameLength,
0040     PWSTR packageFamilyName
0041 );
0042 
0043 // ---
0044 // GetCurrentPackageFullName
0045 // appmodel.h / Kernel32.dll / Windows 8
0046 // ---
0047 typedef LONG (WINAPI *pGetCurrentPackageFullName_t)(
0048     UINT32 *packageFullNameLength,
0049     PWSTR packageFullName
0050 );
0051 
0052 // Flag for `KNOWN_FOLDER_FLAG`, introduced in Win 10 ver 1709, which when
0053 // used with `FOLDERID_LocalAppData` or `FOLDERID_RoamingAppData` when calling
0054 // `SHGetKnownFolderPath`, will return the private app location of the
0055 // packaged app if the current process is a packaged app.
0056 //
0057 // It should return the same values as the `LocalFolder` or `RoamingFolder`
0058 // property of the `Windows.Storage.ApplicationData.Current` UWP API.
0059 //
0060 // ---
0061 // KF_FLAG_FORCE_APP_DATA_REDIRECTION
0062 // shlobj_core.h / Windows 10 v1709
0063 // ---
0064 constexpr int shlobj_KF_FLAG_FORCE_APP_DATA_REDIRECTION = 0x00080000;
0065 
0066 // Flag for `KNOWN_FOLDER_FLAG`, introduced in Win 10 ver 1703, which when
0067 // used within a Desktop Bridge process, will cause the API to return the
0068 // redirected target of the locations.
0069 //
0070 // ---
0071 // KF_FLAG_RETURN_FILTER_REDIRECTION_TARGET
0072 // shlobj_core.h / Windows 10 v1703
0073 // ---
0074 constexpr int shlobj_KF_FLAG_RETURN_FILTER_REDIRECTION_TARGET = 0x00040000;
0075 
0076 struct AppmodelFunctions {
0077     pGetCurrentPackageFamilyName_t pGetCurrentPackageFamilyName;
0078     pGetCurrentPackageFullName_t pGetCurrentPackageFullName;
0079 
0080     AppmodelFunctions() {
0081         HMODULE dllKernel32 = LoadLibraryW(L"kernel32.dll");
0082         pGetCurrentPackageFamilyName = reinterpret_cast<pGetCurrentPackageFamilyName_t>(
0083             GetProcAddress(dllKernel32, "GetCurrentPackageFamilyName"));
0084         pGetCurrentPackageFullName = reinterpret_cast<pGetCurrentPackageFullName_t>(
0085             GetProcAddress(dllKernel32, "GetCurrentPackageFullName"));
0086     }
0087 };
0088 
0089 static const AppmodelFunctions &f()
0090 {
0091     static AppmodelFunctions s_functions;
0092     return s_functions;
0093 } 
0094 
0095 namespace KisWindowsPackageUtils
0096 {
0097 
0098 bool isRunningInPackage()
0099 {
0100     return tryGetCurrentPackageFamilyName(nullptr);
0101 }
0102 
0103 bool tryGetCurrentPackageFamilyName(QString *outName)
0104 {
0105     if (!f().pGetCurrentPackageFamilyName) {
0106         // We are probably on Windows 7 or earlier.
0107         return false;
0108     }
0109 
0110     WCHAR name[appmodel_PACKAGE_FULL_NAME_MAX_LENGTH + 1]; // includes null terminator
0111     UINT32 nameLength = sizeof(name) / sizeof(name[0]);
0112     LONG result = f().pGetCurrentPackageFamilyName(&nameLength, name);
0113     if (result == winerror_APPMODEL_ERROR_NO_PACKAGE) {
0114         // Process not running from a package.
0115         return false;
0116     }
0117     if (result == ERROR_INSUFFICIENT_BUFFER) {
0118         // This shouldn't happen!
0119         qWarning() << "GetCurrentPackageFamilyName returned ERROR_INSUFFICIENT_BUFFER, required length is" << nameLength;
0120         if (outName) {
0121             *outName = QString();
0122         }
0123         return true;
0124     }
0125     if (result != ERROR_SUCCESS) {
0126         qWarning() << "GetCurrentPackageFamilyName returned unexpected error code:" << result;
0127         return false;
0128     }
0129 
0130     if (outName) {
0131         // Sanity check
0132         if (nameLength > sizeof(name) / sizeof(name[0])) {
0133             qWarning() << "GetCurrentPackageFamilyName returned a length exceeding the buffer size:" << nameLength;
0134             nameLength = sizeof(name) / sizeof(name[0]);
0135         }
0136         // Exclude null terminator
0137         if (nameLength > 0 && name[nameLength - 1] == L'\0') {
0138             nameLength -= 1;
0139         }
0140         *outName = QString::fromWCharArray(name, nameLength);
0141     }
0142     return true;
0143 }
0144 
0145 bool tryGetCurrentPackageFullName(QString *outName)
0146 {
0147     if (!f().pGetCurrentPackageFullName) {
0148         // We are probably on Windows 7 or earlier.
0149         return false;
0150     }
0151 
0152     WCHAR name[appmodel_PACKAGE_FULL_NAME_MAX_LENGTH + 1]; // includes null terminator
0153     UINT32 nameLength = sizeof(name) / sizeof(name[0]);
0154     LONG result = f().pGetCurrentPackageFullName(&nameLength, name);
0155     if (result == winerror_APPMODEL_ERROR_NO_PACKAGE) {
0156         // Process not running from a package.
0157         return false;
0158     }
0159     if (result == ERROR_INSUFFICIENT_BUFFER) {
0160         // This shouldn't happen!
0161         qWarning() << "GetCurrentPackageFullName returned ERROR_INSUFFICIENT_BUFFER, required length is" << nameLength;
0162         if (outName) {
0163             *outName = QString();
0164         }
0165         return true;
0166     }
0167     if (result != ERROR_SUCCESS) {
0168         qWarning() << "GetCurrentPackageFullName returned unexpected error code:" << result;
0169         return false;
0170     }
0171 
0172     if (outName) {
0173         // Sanity check
0174         if (nameLength > sizeof(name) / sizeof(name[0])) {
0175             qWarning() << "GetCurrentPackageFullName returned a length exceeding the buffer size:" << nameLength;
0176             nameLength = sizeof(name) / sizeof(name[0]);
0177         }
0178         // Exclude null terminator
0179         if (nameLength > 0 && name[nameLength - 1] == L'\0') {
0180             nameLength -= 1;
0181         }
0182         *outName = QString::fromWCharArray(name, nameLength);
0183     }
0184     return true;
0185 }
0186 
0187 QString getPackageRoamingAppDataLocation()
0188 {
0189     PWSTR path = nullptr;
0190     HRESULT result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, shlobj_KF_FLAG_RETURN_FILTER_REDIRECTION_TARGET, NULL, &path);
0191     if (result != S_OK) {
0192         qWarning() << "SHGetKnownFolderPath returned error HRESULT:" << result;
0193         return QString();
0194     }
0195     if (!path) {
0196         qWarning() << "SHGetKnownFolderPath did not return a path";
0197         return QString();
0198     }
0199     QString appData = QString::fromWCharArray(path);
0200     CoTaskMemFree(path);
0201     return appData;
0202 }
0203 
0204 } /* namespace KisWindowsPackageUtils */