Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e3f36f6c8 | ||
|
|
51fa38f0fe | ||
|
|
67bf8f1568 |
No files matched your search
@@ -64,7 +64,6 @@ jobs:
|
||||
KDE_KWIN_OVERRIDE="$PWD/out/kwin" tools/desktop-kde/build.sh out
|
||||
cp out/kwin/desktop-kde.kwin.txt out/
|
||||
echo "size $(stat -c %s out/desktop-kde.tar.zst) sha256 $(cat out/desktop-kde.sha256)"
|
||||
echo "language packs $(ls out/desktop-kde-i18n-*.tar.zst | wc -l)"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: desktop-kde
|
||||
@@ -75,8 +74,6 @@ jobs:
|
||||
out/desktop-kde.packages.txt
|
||||
out/desktop-kde.kwin.txt
|
||||
out/desktop-kde.sha256
|
||||
out/desktop-kde.i18n.txt
|
||||
out/desktop-kde-i18n-*.tar.zst
|
||||
- name: Publish
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
|
||||
env:
|
||||
@@ -91,6 +88,5 @@ jobs:
|
||||
fi
|
||||
gh release create "$TAG" --latest=false --prerelease=false --title "$TAG" \
|
||||
--notes "KDE Plasma desktop package for DroidDeck's Linux runtime ($GITHUB_REPOSITORY tools/desktop-kde, $GITHUB_SHA). size $(stat -c %s out/desktop-kde.tar.zst), sha256 $(cat out/desktop-kde.sha256). desktop-kde.packages.txt lists the signature-checked Arch Linux ARM packages it was built from." \
|
||||
out/desktop-kde.tar.zst out/desktop-kde.sha256 out/desktop-kde.packages.txt out/desktop-kde.kwin.txt \
|
||||
out/desktop-kde.i18n.txt out/desktop-kde-i18n-*.tar.zst
|
||||
out/desktop-kde.tar.zst out/desktop-kde.sha256 out/desktop-kde.packages.txt out/desktop-kde.kwin.txt
|
||||
gh release edit "$TAG" --latest=false --prerelease=false
|
||||
@@ -47,7 +47,7 @@ Compatibility and performance vary by device; hardware validation is limited. De
|
||||
|
||||
## Credits and licence
|
||||
|
||||
GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). Epic Games and GOG support build on [Phobos’s](https://github.com/phobos665) adaptation of [Legendary](https://github.com/legendary-gl/legendary) and [gogdl](https://github.com/Heroic-Games-Launcher/heroic-gogdl), respectively. LSFG frame generation is from the work of Camille LaVey and the [Eden](https://eden-emu.dev) emulator project, following [lsfg-vk](https://github.com/PancakeTAS/lsfg-vk), ported to WinNative and DroidDeck by [@maxjivi05](https://github.com/maxjivi05); it needs your own copy of [Lossless Scaling](https://store.steampowered.com/app/993090/) and ships none of its shaders. x86 AppImages, and any whose own runtime cannot unpack them, are unpacked with [uruntime](https://github.com/VHSgunzo/uruntime) by VHSgunzo (MIT), shipped unmodified with its licence. The Steam client's audio goes through [@The412Banner](https://github.com/The412Banner)'s [DirectAudio](https://github.com/The412Banner/directaudio) PulseAudio sink (LGPL-2.1-or-later), built from `tools/directaudio/sink` with its request size kept to its ring so clients are not asked to queue 100 ms ahead. mmap ring-buffer controller input was developed by ribbit384 ([WinNative #345](https://github.com/WinNative-Emu/WinNative/pull/345)). Why the x86-64 Steam client refused the arm64 client's sign-in was checked by reading Steam's saved token with the `ConnectCache` format from [deadlock-api-ingest](https://github.com/deadlock-api/deadlock-api-ingest). See [LICENSE](LICENSE). Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.
|
||||
GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). Epic Games and GOG support build on [Phobos’s](https://github.com/phobos665) adaptation of [Legendary](https://github.com/legendary-gl/legendary) and [gogdl](https://github.com/Heroic-Games-Launcher/heroic-gogdl), respectively. LSFG frame generation is from the work of Camille LaVey and the [Eden](https://eden-emu.dev) emulator project, following [lsfg-vk](https://github.com/PancakeTAS/lsfg-vk), ported to WinNative and DroidDeck by [@maxjivi05](https://github.com/maxjivi05); it needs your own copy of [Lossless Scaling](https://store.steampowered.com/app/993090/) and ships none of its shaders. x86 AppImages, and any whose own runtime cannot unpack them, are unpacked with [uruntime](https://github.com/VHSgunzo/uruntime) by VHSgunzo (MIT), shipped unmodified with its licence. mmap ring-buffer controller input was developed by ribbit384 ([WinNative #345](https://github.com/WinNative-Emu/WinNative/pull/345)). See [LICENSE](LICENSE). Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.
|
||||
|
||||
XR glasses support builds on WinNative's VITURE glasses work. VITURE protocol details were checked against [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver) by [@wheaney](https://github.com/wheaney), including the VITURE protocol header it ships, [ar-drivers-rs](https://github.com/badicsalex/ar-drivers-rs) by [@badicsalex](https://github.com/badicsalex) and the [go-macos/iokit VITURE package](https://pkg.go.dev/github.com/go-macos/iokit/viture); the optional VITURE SDK belongs to VITURE and is not included. RayNeo support adapts [@informalTechCode](https://github.com/informalTechCode)'s [RayNeo-Air-Series-3DoF-Scaffold](https://github.com/informalTechCode/RayNeo-Air-Series-3DoF-Scaffold) (Apache-2.0) and references [@verncat](https://github.com/verncat)'s [RayNeo-Air-3S-Pro-OpenVR](https://github.com/verncat/RayNeo-Air-3S-Pro-OpenVR) (MIT) and [Monado](https://gitlab.freedesktop.org/monado/monado) (BSL-1.0); see [docs/third-party/rayneo](docs/third-party/rayneo/README.md). XREAL 1S support references [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver) and [xrealOneDeviceKit](https://github.com/wheaney/xrealOneDeviceKit) by @wheaney, [xreal_one_driver](https://github.com/rohitsangwan01/xreal_one_driver) by [@rohitsangwan01](https://github.com/rohitsangwan01) (MIT) and [xr-tools](https://github.com/0xcaff/xr-tools) by [@0xcaff](https://github.com/0xcaff). The REDMAGIC glasses-display findings draw on [@mekhontsev](https://github.com/mekhontsev)'s [MagicDesk investigation](https://github.com/mekhontsev/magicdesk/blob/main/docs/xr-resolution-fix.md) and [@Coding-BR](https://github.com/Coding-BR)'s [decompiled Nubia kernel](https://github.com/Coding-BR/android_kernel_nubia_sm8850_qwjujube). VITURE, RayNeo, XREAL and REDMAGIC are trademarks of their owners; this project is not affiliated with them.
|
||||
|
||||
|
||||
@@ -192,8 +192,6 @@ android {
|
||||
unitTests.returnDefaultValues = true
|
||||
unitTests.includeAndroidResources = true
|
||||
unitTests.all {
|
||||
// Robolectric keeps a sandbox per SDK level the tests ask for; seven no longer fit in Gradle's default 512 MB.
|
||||
maxHeapSize = '2g'
|
||||
def logTarget = providers.systemProperty('robolectric.logging').orNull
|
||||
if (logTarget != null) systemProperty 'robolectric.logging', logTarget
|
||||
}
|
||||
|
||||
@@ -65,6 +65,11 @@
|
||||
android:exported="true"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density|keyboard|keyboardHidden|navigation">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.CAR_LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@@ -83,31 +88,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity-alias
|
||||
android:name=".LauncherDark"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.CAR_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".LauncherLight"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:icon="@mipmap/ic_launcher_light"
|
||||
android:enabled="false"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.CAR_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity
|
||||
android:name=".HomeActivity"
|
||||
android:enabled="false"
|
||||
|
||||
@@ -11,23 +11,23 @@ add_subdirectory(adrenotools)
|
||||
# DXVK's DXBC->SPIR-V translator is zlib licensed (vendored under thirdparty/dxbc).
|
||||
add_subdirectory(thirdparty/dxbc)
|
||||
|
||||
set(DDFG_UPSTREAM "v0.3.0")
|
||||
set(DDFG_CHAIN_SRC
|
||||
framegen/ddfg/framegen.cpp
|
||||
framegen/ddfg/framegen.hpp
|
||||
framegen/ddfg/record_impl.inc
|
||||
framegen/ddfg/config.hpp
|
||||
framegen/ddfg/embedded_shaders.hpp
|
||||
framegen/ddfg/ddfg_engine.cpp
|
||||
set(WINFG_UPSTREAM "v0.3.0")
|
||||
set(WINFG_CHAIN_SRC
|
||||
framegen/winfg/framegen.cpp
|
||||
framegen/winfg/framegen.hpp
|
||||
framegen/winfg/record_impl.inc
|
||||
framegen/winfg/config.hpp
|
||||
framegen/winfg/embedded_shaders.hpp
|
||||
framegen/winfg/winfg_engine.cpp
|
||||
)
|
||||
set(DDFG_HASH_INPUT "")
|
||||
foreach(f ${DDFG_CHAIN_SRC})
|
||||
set(WINFG_HASH_INPUT "")
|
||||
foreach(f ${WINFG_CHAIN_SRC})
|
||||
file(SHA256 "${CMAKE_CURRENT_SOURCE_DIR}/${f}" _h)
|
||||
string(APPEND DDFG_HASH_INPUT "${_h}")
|
||||
string(APPEND WINFG_HASH_INPUT "${_h}")
|
||||
endforeach()
|
||||
string(SHA256 DDFG_CHAIN_HASH_FULL "${DDFG_HASH_INPUT}")
|
||||
string(SUBSTRING "${DDFG_CHAIN_HASH_FULL}" 0 12 DDFG_CHAIN_HASH)
|
||||
message(STATUS "dd-fg chain: win-fg ${DDFG_UPSTREAM} (src ${DDFG_CHAIN_HASH})")
|
||||
string(SHA256 WINFG_CHAIN_HASH_FULL "${WINFG_HASH_INPUT}")
|
||||
string(SUBSTRING "${WINFG_CHAIN_HASH_FULL}" 0 12 WINFG_CHAIN_HASH)
|
||||
message(STATUS "win-fg chain: ${WINFG_UPSTREAM} (src ${WINFG_CHAIN_HASH})")
|
||||
|
||||
add_subdirectory(waylandcomp)
|
||||
add_subdirectory(questxr)
|
||||
|
||||
@@ -131,7 +131,7 @@ struct VkTable {
|
||||
#include <string>
|
||||
|
||||
namespace lsfg { class Engine; }
|
||||
namespace ddfg { class Engine; }
|
||||
namespace winfg { class Engine; }
|
||||
|
||||
static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2;
|
||||
|
||||
@@ -280,7 +280,7 @@ public:
|
||||
static constexpr int32_t kFgCaptureGame = -1;
|
||||
void setFrameGenTuning(float flowScale, float refreshHz, int32_t captureHeight = 0);
|
||||
void setFrameGenEngine(int kind);
|
||||
void setDdFgTuning(int model, int perfPreset);
|
||||
void setWinFgTuning(int model, int perfPreset);
|
||||
|
||||
private:
|
||||
struct WinTex {
|
||||
@@ -468,12 +468,12 @@ private:
|
||||
uint64_t fgSourceFrames_ = 0;
|
||||
std::string lsfgCachePath_;
|
||||
bool lsfgEngineTried_ = false;
|
||||
std::unique_ptr<ddfg::Engine> ddfgEngine_;
|
||||
bool ddfgEngineTried_ = false;
|
||||
std::unique_ptr<winfg::Engine> winfgEngine_;
|
||||
bool winfgEngineTried_ = false;
|
||||
std::atomic<int> fgEngineKind_{0};
|
||||
std::atomic<int> fgModel_{4};
|
||||
std::atomic<int> fgPerfPreset_{1};
|
||||
bool ensureDdFgEngine();
|
||||
bool ensureWinFgEngine();
|
||||
bool fgCapsOk() const;
|
||||
|
||||
uint32_t syncSlot(uint32_t k) const { return currentFrame * kMaxPresentsPerFrame + k; }
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#pragma once
|
||||
#include "vk_dispatch.hpp"
|
||||
struct VkTable;
|
||||
bool ddfgVkdInit(const VkTable& table, ddfg::DeviceDispatch& dd, ddfg::InstanceDispatch& id);
|
||||
@@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
#if defined(__ANDROID__)
|
||||
#include <android/log.h>
|
||||
#define DDFG_LOGI(...) __android_log_print(ANDROID_LOG_INFO, "dd-fg", __VA_ARGS__)
|
||||
#define DDFG_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "dd-fg", __VA_ARGS__)
|
||||
#else
|
||||
#include <cstdio>
|
||||
#define DDFG_LOGI(...) do { std::fprintf(stderr, "[dd-fg] " __VA_ARGS__); std::fprintf(stderr, "\n"); } while(0)
|
||||
#define DDFG_LOGE(...) do { std::fprintf(stderr, "[dd-fg][E] " __VA_ARGS__); std::fprintf(stderr, "\n"); } while(0)
|
||||
#endif
|
||||
|
||||
#define DDFG_LOGD(cond, ...) do { if (cond) DDFG_LOGI(__VA_ARGS__); } while(0)
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace ddfg {
|
||||
namespace winfg {
|
||||
|
||||
struct Config {
|
||||
bool enabled = false;
|
||||
@@ -213,48 +213,48 @@ static inline void apply_toml(Config& c, const std::string& path) {
|
||||
}
|
||||
|
||||
static inline std::string conf_path() {
|
||||
if (const char* p = std::getenv("DD_FG_CONF")) return p;
|
||||
if (const char* p = std::getenv("WIN_FG_CONF")) return p;
|
||||
const char* home = std::getenv("HOME");
|
||||
return home ? std::string(home) + "/.config/dd-fg/conf.toml" : std::string();
|
||||
return home ? std::string(home) + "/.config/win-fg/conf.toml" : std::string();
|
||||
}
|
||||
|
||||
static inline std::string capture_root(const Config& c) {
|
||||
if (!c.capDir.empty()) return c.capDir;
|
||||
if (const char* home = std::getenv("HOME")) return std::string(home) + "/.cache/ddfg-capture";
|
||||
return std::string("/tmp/ddfg-capture");
|
||||
if (const char* home = std::getenv("HOME")) return std::string(home) + "/.cache/winfg-capture";
|
||||
return std::string("/tmp/winfg-capture");
|
||||
}
|
||||
|
||||
static inline Config load_config() {
|
||||
Config c;
|
||||
c.enabled = envi("DD_FG_ENABLE", 0) != 0;
|
||||
if (const char* d = std::getenv("DD_FG_DEBUG")) c.debug = parse_bool(d, c.debug);
|
||||
if (const char* pc = std::getenv("DD_FG_PACING")) c.pacing = parse_bool(pc, c.pacing);
|
||||
c.model = envi("DD_FG_MODEL", c.model);
|
||||
c.multiplier = envi("DD_FG_MULT", c.multiplier);
|
||||
c.extraImages = envi("DD_FG_EXTRA_IMAGES", c.extraImages);
|
||||
c.perfPreset = envi("DD_FG_PERF_PRESET", c.perfPreset);
|
||||
if (const char* g = std::getenv("DD_FG_GM")) c.gmMode = parse_tristate(g, c.gmMode);
|
||||
if (const char* r = std::getenv("DD_FG_FLOWREG")) c.frMode = parse_tristate(r, c.frMode);
|
||||
c.frIters = envi("DD_FG_FR_ITERS", c.frIters);
|
||||
c.frLambda = envf("DD_FG_FR_LAMBDA", c.frLambda);
|
||||
c.frDt = envf("DD_FG_FR_DT", c.frDt);
|
||||
c.frEdge = envf("DD_FG_FR_EDGE", c.frEdge);
|
||||
c.frEps = envf("DD_FG_FR_EPS", c.frEps);
|
||||
c.flowScale = envf("DD_FG_FLOWSCALE", c.flowScale);
|
||||
c.beta = envf("DD_FG_BETA", c.beta);
|
||||
c.lambda = envf("DD_FG_LAMBDA", c.lambda);
|
||||
c.epsilon = envf("DD_FG_EPSILON", c.epsilon);
|
||||
c.photoScale = envf("DD_FG_PHOTOSCALE", c.photoScale);
|
||||
if (const char* cc = std::getenv("DD_FG_CAPTURE")) c.capture = parse_bool(cc, c.capture);
|
||||
c.captureW = envi("DD_FG_CAPTURE_W", c.captureW);
|
||||
c.captureH = envi("DD_FG_CAPTURE_H", c.captureH);
|
||||
if (const char* cm = std::getenv("DD_FG_CAPTURE_MODE")) c.capMode = parse_capmode(cm, c.capMode);
|
||||
c.capPatches = envi("DD_FG_CAPTURE_PATCHES", c.capPatches);
|
||||
c.capPatchSize = envi("DD_FG_CAPTURE_PATCH", c.capPatchSize);
|
||||
c.capMotion = envf("DD_FG_CAPTURE_MOTION", c.capMotion);
|
||||
c.capShardMB = envi("DD_FG_CAPTURE_SHARD_MB", c.capShardMB);
|
||||
if (const char* cd = std::getenv("DD_FG_CAPTURE_DIR")) c.capDir = cd;
|
||||
if (const char* h = std::getenv("DD_FG_HUD_RECT")) {
|
||||
c.enabled = envi("WIN_FG_ENABLE", 0) != 0;
|
||||
if (const char* d = std::getenv("WIN_FG_DEBUG")) c.debug = parse_bool(d, c.debug);
|
||||
if (const char* pc = std::getenv("WIN_FG_PACING")) c.pacing = parse_bool(pc, c.pacing);
|
||||
c.model = envi("WIN_FG_MODEL", c.model);
|
||||
c.multiplier = envi("WIN_FG_MULT", c.multiplier);
|
||||
c.extraImages = envi("WIN_FG_EXTRA_IMAGES", c.extraImages);
|
||||
c.perfPreset = envi("WIN_FG_PERF_PRESET", c.perfPreset);
|
||||
if (const char* g = std::getenv("WIN_FG_GM")) c.gmMode = parse_tristate(g, c.gmMode);
|
||||
if (const char* r = std::getenv("WIN_FG_FLOWREG")) c.frMode = parse_tristate(r, c.frMode);
|
||||
c.frIters = envi("WIN_FG_FR_ITERS", c.frIters);
|
||||
c.frLambda = envf("WIN_FG_FR_LAMBDA", c.frLambda);
|
||||
c.frDt = envf("WIN_FG_FR_DT", c.frDt);
|
||||
c.frEdge = envf("WIN_FG_FR_EDGE", c.frEdge);
|
||||
c.frEps = envf("WIN_FG_FR_EPS", c.frEps);
|
||||
c.flowScale = envf("WIN_FG_FLOWSCALE", c.flowScale);
|
||||
c.beta = envf("WIN_FG_BETA", c.beta);
|
||||
c.lambda = envf("WIN_FG_LAMBDA", c.lambda);
|
||||
c.epsilon = envf("WIN_FG_EPSILON", c.epsilon);
|
||||
c.photoScale = envf("WIN_FG_PHOTOSCALE", c.photoScale);
|
||||
if (const char* cc = std::getenv("WIN_FG_CAPTURE")) c.capture = parse_bool(cc, c.capture);
|
||||
c.captureW = envi("WIN_FG_CAPTURE_W", c.captureW);
|
||||
c.captureH = envi("WIN_FG_CAPTURE_H", c.captureH);
|
||||
if (const char* cm = std::getenv("WIN_FG_CAPTURE_MODE")) c.capMode = parse_capmode(cm, c.capMode);
|
||||
c.capPatches = envi("WIN_FG_CAPTURE_PATCHES", c.capPatches);
|
||||
c.capPatchSize = envi("WIN_FG_CAPTURE_PATCH", c.capPatchSize);
|
||||
c.capMotion = envf("WIN_FG_CAPTURE_MOTION", c.capMotion);
|
||||
c.capShardMB = envi("WIN_FG_CAPTURE_SHARD_MB", c.capShardMB);
|
||||
if (const char* cd = std::getenv("WIN_FG_CAPTURE_DIR")) c.capDir = cd;
|
||||
if (const char* h = std::getenv("WIN_FG_HUD_RECT")) {
|
||||
float r[4] = {0,0,0,0}; int n = 0;
|
||||
std::string s = h; size_t start = 0;
|
||||
for (; n < 4; ++n) {
|
||||
@@ -266,8 +266,8 @@ static inline Config load_config() {
|
||||
c.hudX0 = r[0]; c.hudY0 = r[1]; c.hudX1 = r[2]; c.hudY1 = r[3];
|
||||
}
|
||||
const char* home = std::getenv("HOME");
|
||||
if (home) apply_toml(c, std::string(home) + "/.config/dd-fg/conf.toml");
|
||||
if (const char* p = std::getenv("DD_FG_CONF")) apply_toml(c, p);
|
||||
if (home) apply_toml(c, std::string(home) + "/.config/win-fg/conf.toml");
|
||||
if (const char* p = std::getenv("WIN_FG_CONF")) apply_toml(c, p);
|
||||
c.sanitize();
|
||||
return c;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
namespace ddfg { namespace embedded {
|
||||
namespace winfg { namespace embedded {
|
||||
static const uint32_t OF3_LUMA_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000054u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000000du,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00030005u,0x00000009u,0x00646967u,0x00080005u,0x0000000du,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000012u,0x006d6964u,0x00040005u,0x00000016u,0x74734475u,0x00000000u,0x00030005u,0x00000030u,0x00007675u,0x00030005u,0x0000003bu,0x00626772u,0x00040005u,0x0000003fu,0x63725375u,0x00000000u,0x00030005u,0x00000047u,0x00005966u,0x00040047u,0x0000000du,0x0000000bu,0x0000001cu,0x00030047u,0x00000016u,0x00000019u,0x00040047u,0x00000016u,0x00000021u,0x00000030u,0x00040047u,0x00000016u,0x00000022u,0x00000000u,0x00040047u,0x0000003fu,0x00000021u,0x00000020u,0x00040047u,0x0000003fu,0x00000022u,0x00000000u,0x00040047u,0x00000053u,0x0000000bu,0x00000019u,0x00020013u,0x00000002u,0x00030021u,0x00000003u,0x00000002u,0x00040015u,0x00000006u,0x00000020u,0x00000001u,0x00040017u,0x00000007u,0x00000006u,0x00000002u,0x00040020u,0x00000008u,0x00000007u,0x00000007u,0x00040015u,0x0000000au,0x00000020u,0x00000000u,0x00040017u,0x0000000bu,0x0000000au,0x00000003u,0x00040020u,0x0000000cu,0x00000001u,0x0000000bu,0x0004003bu,0x0000000cu,0x0000000du,0x00000001u,0x00040017u,0x0000000eu,0x0000000au,0x00000002u,0x00030016u,0x00000013u,0x00000020u,0x00090019u,0x00000014u,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000002u,0x00000003u,0x00040020u,0x00000015u,0x00000000u,0x00000014u,0x0004003bu,0x00000015u,0x00000016u,0x00000000u,0x00020014u,0x00000019u,0x0004002bu,0x0000000au,0x0000001au,0x00000000u,0x00040020u,0x0000001bu,0x00000007u,0x00000006u,0x0004002bu,0x0000000au,0x00000024u,0x00000001u,0x00040017u,0x0000002eu,0x00000013u,0x00000002u,0x00040020u,0x0000002fu,0x00000007u,0x0000002eu,0x0004002bu,0x00000013u,0x00000033u,0x3f000000u,0x00040017u,0x00000039u,0x00000013u,0x00000003u,0x00040020u,0x0000003au,0x00000007u,0x00000039u,0x00090019u,0x0000003cu,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000001u,0x00000000u,0x0003001bu,0x0000003du,0x0000003cu,0x00040020u,0x0000003eu,0x00000000u,0x0000003du,0x0004003bu,0x0000003eu,0x0000003fu,0x00000000u,0x0004002bu,0x00000013u,0x00000042u,0x00000000u,0x00040017u,0x00000043u,0x00000013u,0x00000004u,0x00040020u,0x00000046u,0x00000007u,0x00000013u,0x0004002bu,0x00000013u,0x00000049u,0x3e59b3d0u,0x0004002bu,0x00000013u,0x0000004au,0x3f371759u,0x0004002bu,0x00000013u,0x0000004bu,0x3d93dd98u,0x0006002cu,0x00000039u,0x0000004cu,0x00000049u,0x0000004au,0x0000004bu,0x0004002bu,0x0000000au,0x00000052u,0x00000010u,0x0006002cu,0x0000000bu,0x00000053u,0x00000052u,0x00000052u,0x00000024u,0x00050036u,0x00000002u,0x00000004u,0x00000000u,0x00000003u,0x000200f8u,0x00000005u,0x0004003bu,0x00000008u,0x00000009u,0x00000007u,0x0004003bu,0x00000008u,0x00000012u,0x00000007u,0x0004003bu,0x0000002fu,0x00000030u,0x00000007u,0x0004003bu,0x0000003au,0x0000003bu,0x00000007u,0x0004003bu,0x00000046u,0x00000047u,0x00000007u,0x0004003du,0x0000000bu,0x0000000fu,0x0000000du,0x0007004fu,0x0000000eu,0x00000010u,0x0000000fu,0x0000000fu,0x00000000u,0x00000001u,0x0004007cu,0x00000007u,0x00000011u,0x00000010u,0x0003003eu,0x00000009u,0x00000011u,0x0004003du,0x00000014u,0x00000017u,0x00000016u,0x00040068u,0x00000007u,0x00000018u,0x00000017u,0x0003003eu,0x00000012u,0x00000018u,0x00050041u,0x0000001bu,0x0000001cu,0x00000009u,0x0000001au,0x0004003du,0x00000006u,0x0000001du,0x0000001cu,0x00050041u,0x0000001bu,0x0000001eu,0x00000012u,0x0000001au,0x0004003du,0x00000006u,0x0000001fu,0x0000001eu,0x000500afu,0x00000019u,0x00000020u,0x0000001du,0x0000001fu,0x000400a8u,0x00000019u,0x00000021u,0x00000020u,0x000300f7u,0x00000023u,0x00000000u,0x000400fau,0x00000021u,0x00000022u,0x00000023u,0x000200f8u,0x00000022u,0x00050041u,0x0000001bu,0x00000025u,0x00000009u,0x00000024u,0x0004003du,0x00000006u,0x00000026u,0x00000025u,0x00050041u,0x0000001bu,0x00000027u,0x00000012u,0x00000024u,0x0004003du,0x00000006u,0x00000028u,0x00000027u,0x000500afu,0x00000019u,0x00000029u,0x00000026u,0x00000028u,0x000200f9u,0x00000023u,0x000200f8u,0x00000023u,0x000700f5u,0x00000019u,0x0000002au,0x00000020u,0x00000005u,0x00000029u,0x00000022u,0x000300f7u,0x0000002cu,0x00000000u,0x000400fau,0x0000002au,0x0000002bu,0x0000002cu,0x000200f8u,0x0000002bu,0x000100fdu,0x000200f8u,0x0000002cu,0x0004003du,0x00000007u,0x00000031u,0x00000009u,0x0004006fu,0x0000002eu,0x00000032u,0x00000031u,0x00050050u,0x0000002eu,0x00000034u,0x00000033u,0x00000033u,0x00050081u,0x0000002eu,0x00000035u,0x00000032u,0x00000034u,0x0004003du,0x00000007u,0x00000036u,0x00000012u,0x0004006fu,0x0000002eu,0x00000037u,0x00000036u,0x00050088u,0x0000002eu,0x00000038u,0x0Line truncated
|
||||
static const size_t OF3_LUMA_LEN = 481;
|
||||
static const uint32_t OF3_DOWNSAMPLE_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x0000008au,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000000du,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00030005u,0x00000009u,0x00646967u,0x00080005u,0x0000000du,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000012u,0x006d6964u,0x00040005u,0x00000016u,0x74734475u,0x00000000u,0x00040005u,0x00000030u,0x44637273u,0x00006d69u,0x00040005u,0x00000034u,0x63725375u,0x00000000u,0x00040005u,0x0000003au,0x65736162u,0x00000000u,0x00030005u,0x00000044u,0x00000068u,0x00030005u,0x0000004au,0x00000061u,0x00030005u,0x00000059u,0x00000062u,0x00030005u,0x00000065u,0x00000063u,0x00030005u,0x00000071u,0x00000064u,0x00040047u,0x0000000du,0x0000000bu,0x0000001cu,0x00030047u,0x00000016u,0x00000019u,0x00040047u,0x00000016u,0x00000021u,0x00000030u,0x00040047u,0x00000016u,0x00000022u,0x00000000u,0x00040047u,0x00000034u,0x00000021u,0x00000020u,0x00040047u,0x00000034u,0x00000022u,0x00000000u,0x00040047u,0x00000089u,0x0000000bu,0x00000019u,0x00020013u,0x00000002u,0x00030021u,0x00000003u,0x00000002u,0x00040015u,0x00000006u,0x00000020u,0x00000001u,0x00040017u,0x00000007u,0x00000006u,0x00000002u,0x00040020u,0x00000008u,0x00000007u,0x00000007u,0x00040015u,0x0000000au,0x00000020u,0x00000000u,0x00040017u,0x0000000bu,0x0000000au,0x00000003u,0x00040020u,0x0000000cu,0x00000001u,0x0000000bu,0x0004003bu,0x0000000cu,0x0000000du,0x00000001u,0x00040017u,0x0000000eu,0x0000000au,0x00000002u,0x00030016u,0x00000013u,0x00000020u,0x00090019u,0x00000014u,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000002u,0x00000003u,0x00040020u,0x00000015u,0x00000000u,0x00000014u,0x0004003bu,0x00000015u,0x00000016u,0x00000000u,0x00020014u,0x00000019u,0x0004002bu,0x0000000au,0x0000001au,0x00000000u,0x00040020u,0x0000001bu,0x00000007u,0x00000006u,0x0004002bu,0x0000000au,0x00000024u,0x00000001u,0x00040017u,0x0000002eu,0x00000013u,0x00000002u,0x00040020u,0x0000002fu,0x00000007u,0x0000002eu,0x00090019u,0x00000031u,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000001u,0x00000000u,0x0003001bu,0x00000032u,0x00000031u,0x00040020u,0x00000033u,0x00000000u,0x00000032u,0x0004003bu,0x00000033u,0x00000034u,0x00000000u,0x0004002bu,0x00000006u,0x00000036u,0x00000000u,0x0004002bu,0x00000013u,0x0000003du,0x40000000u,0x0004002bu,0x00000013u,0x0000003fu,0x3f800000u,0x0004002bu,0x00000013u,0x00000045u,0x3f000000u,0x00040020u,0x00000049u,0x00000007u,0x00000013u,0x0004002bu,0x00000013u,0x00000055u,0x00000000u,0x00040017u,0x00000056u,0x00000013u,0x00000004u,0x0004002bu,0x00000013u,0x00000085u,0x3e800000u,0x0004002bu,0x0000000au,0x00000088u,0x00000010u,0x0006002cu,0x0000000bu,0x00000089u,0x00000088u,0x00000088u,0x00000024u,0x00050036u,0x00000002u,0x00000004u,0x00000000u,0x00000003u,0x000200f8u,0x00000005u,0x0004003bu,0x00000008u,0x00000009u,0x00000007u,0x0004003bu,0x00000008u,0x00000012u,0x00000007u,0x0004003bu,0x0000002fu,0x00000030u,0x00000007u,0x0004003bu,0x0000002fu,0x0000003au,0x00000007u,0x0004003bu,0x0000002fu,0x00000044u,0x00000007u,0x0004003bu,0x00000049u,0x0000004au,0x00000007u,0x0004003bu,0x00000049u,0x00000059u,0x00000007u,0x0004003bu,0x00000049u,0x00000065u,0x00000007u,0x0004003bu,0x00000049u,0x00000071u,0x00000007u,0x0004003du,0x0000000bu,0x0000000fu,0x0000000du,0x0007004fu,0x0000000eu,0x00000010u,0x0000000fu,0x0000000fu,0x00000000u,0x00000001u,0x0004007cu,0x00000007u,0x00000011u,0x00000010u,0x0003003eu,0x00000009u,0x00000011u,0x0004003du,0x00000014u,0x00000017u,0x00000016u,0x00040068u,0x00000007u,0x00000018u,0x00000017u,0x0003003eu,0x00000012u,0x00000018u,0x00050041u,0x0000001bu,0x0000001cu,0x00000009u,0x0000001au,0x0004003du,0x00000006u,0x0000001du,0x0000001cu,0x00050041u,0x0000001bu,0x0000001eu,0x00000012u,0x0000001au,0x0004003du,0x00000006u,0x0000001fu,0x0000001eu,0x000500afu,0x00000019u,0x00000020u,0x0000001du,0x0000001fu,0x000400a8u,0x00000019u,0x00000021u,0x00000020u,0x000300f7u,0x00000023u,0x00000000u,0x000400fau,0x00000021u,0x00000022u,0x00000023u,0x000200f8u,0x00000022u,0x00050041u,0x0000001bu,0x00000025u,0x00000009u,0x00000024u,0x0004003du,0x00000006u,0x00000026u,0x00000025u,0x00050041u,0x0000001bu,0x00000027u,0x00000012u,0x00000024u,0x0004003du,0x00000006u,0x00000028u,0x00000027u,0x000500afu,0x00000019u,0x00000029u,0x00000026u,0x00000028u,0x000200f9u,0x00000023u,0x000200f8u,0x00000023u,0x000700f5u,0x00000019u,0x0000002au,0x00000020u,0x00000005u,0x00000029u,0x00000022u,0x000300f7u,0x0000002cu,0x00000000u,0x000400fau,0x0000002au,0x0000002bu,0x0000002cu,0x000200f8u,0x0000002bu,0x000100fdu,0x000200f8u,0x0000002cu,0x0004003du,0x00000032u,0x00000035u,0x00000034u,0x00040064u,0x00000031u,0x00000037u,0x00000035u,0x0005006Line truncated
|
||||
@@ -20,10 +20,10 @@ static const uint32_t OF3_GM_PREWARP_SPV[] = {0x07230203u,0x00010300u,0x0008000b
|
||||
static const size_t OF3_GM_PREWARP_LEN = 706;
|
||||
static const uint32_t OF3_FLOWREG_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000144u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x00000037u,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00060005u,0x0000000eu,0x63746566u,0x76285568u,0x763b3269u,0x003b3269u,0x00030005u,0x0000000cu,0x00000070u,0x00030005u,0x0000000du,0x006d6964u,0x00060005u,0x00000013u,0x63746566u,0x76284c68u,0x763b3269u,0x003b3269u,0x00030005u,0x00000011u,0x00000070u,0x00030005u,0x00000012u,0x006d6964u,0x00040005u,0x00000018u,0x6f6c4675u,0x006e4977u,0x00040005u,0x00000026u,0x6d754c75u,0x00000061u,0x00030005u,0x00000034u,0x00646967u,0x00080005u,0x00000037u,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x0000003cu,0x006d6964u,0x00030005u,0x00000055u,0x00007464u,0x00030005u,0x00000056u,0x00000055u,0x00050006u,0x00000056u,0x00000000u,0x61726170u,0x0000736du,0x00030005u,0x00000058u,0x00000000u,0x00040005u,0x0000005cu,0x626d616cu,0x00006164u,0x00050005u,0x0000005fu,0x65676465u,0x68706c41u,0x00000061u,0x00040005u,0x00000063u,0x54737065u,0x00000056u,0x00030005u,0x00000068u,0x00004375u,0x00040005u,0x00000069u,0x61726170u,0x0000006du,0x00040005u,0x0000006bu,0x61726170u,0x0000006du,0x00030005u,0x0000006eu,0x00004c75u,0x00040005u,0x00000073u,0x61726170u,0x0000006du,0x00040005u,0x00000074u,0x61726170u,0x0000006du,0x00030005u,0x00000077u,0x00005275u,0x00040005u,0x0000007bu,0x61726170u,0x0000006du,0x00040005u,0x0000007cu,0x61726170u,0x0000006du,0x00030005u,0x0000007fu,0x00005575u,0x00040005u,0x00000083u,0x61726170u,0x0000006du,0x00040005u,0x00000084u,0x61726170u,0x0000006du,0x00030005u,0x00000087u,0x00004475u,0x00040005u,0x0000008bu,0x61726170u,0x0000006du,0x00040005u,0x0000008cu,0x61726170u,0x0000006du,0x00040005u,0x0000008fu,0x6d69646cu,0x00000000u,0x00030005u,0x00000093u,0x00004c6cu,0x00040005u,0x00000096u,0x61726170u,0x0000006du,0x00040005u,0x00000097u,0x61726170u,0x0000006du,0x00030005u,0x0000009au,0x0000526cu,0x00040005u,0x0000009du,0x61726170u,0x0000006du,0x00040005u,0x0000009eu,0x61726170u,0x0000006du,0x00030005u,0x000000a1u,0x0000556cu,0x00040005u,0x000000a4u,0x61726170u,0x0000006du,0x00040005u,0x000000a5u,0x61726170u,0x0000006du,0x00030005u,0x000000a8u,0x0000446cu,0x00040005u,0x000000abu,0x61726170u,0x0000006du,0x00040005u,0x000000acu,0x61726170u,0x0000006du,0x00040005u,0x000000afu,0x67616d67u,0x00000000u,0x00030005u,0x000000bbu,0x00000067u,0x00030005u,0x000000c4u,0x00004c64u,0x00030005u,0x000000c8u,0x00005264u,0x00030005u,0x000000ccu,0x00005564u,0x00030005u,0x000000d0u,0x00004464u,0x00030005u,0x000000d4u,0x00003265u,0x00030005u,0x000000d8u,0x00004c77u,0x00030005u,0x000000e1u,0x00005277u,0x00030005u,0x000000eau,0x00005577u,0x00030005u,0x000000f3u,0x00004477u,0x00040005u,0x000000fcu,0x576d7573u,0x00000000u,0x00040005u,0x00000104u,0x576d7573u,0x00000055u,0x00040005u,0x00000114u,0x66694475u,0x00000066u,0x00030005u,0x00000120u,0x00003066u,0x00050005u,0x00000121u,0x6f6c4675u,0x73624f77u,0x00000000u,0x00040005u,0x0000012au,0x74656874u,0x00000061u,0x00030005u,0x0000012eu,0x00000072u,0x00040005u,0x00000132u,0x77654e75u,0x00000000u,0x00050005u,0x0000013eu,0x6f6c4675u,0x74754f77u,0x00000000u,0x00040047u,0x00000018u,0x00000021u,0x00000020u,0x00040047u,0x00000018u,0x00000022u,0x00000000u,0x00040047u,0x00000026u,0x00000021u,0x00000022u,0x00040047u,0x00000026u,0x00000022u,0x00000000u,0x00040047u,0x00000037u,0x0000000bu,0x0000001cu,0x00030047u,0x00000056u,0x00000002u,0x00050048u,0x00000056u,0x00000000u,0x00000023u,0x00000000u,0x00040047u,0x00000058u,0x00000021u,0x00000000u,0x00040047u,0x00000058u,0x00000022u,0x00000000u,0x00040047u,0x00000121u,0x00000021u,0x00000021u,0x00040047u,0x00000121u,0x00000022u,0x00000000u,0x00030047u,0x0000013eu,0x00000019u,0x00040047u,0x0000013eu,0x00000021u,0x00000030u,0x00040047u,0x0000013eu,0x00000022u,0x00000000u,0x00040047u,0x00000143u,0x0000000bu,0x00000019u,0x00020013u,0x00000002u,0x00030021u,0x00000003u,0x00000002u,0x00040015u,0x00000006u,0x00000020u,0x00000001u,0x00040017u,0x00000007u,0x00000006u,0x00000002u,0x00040020u,0x00000008u,0x00000007u,0x00000007u,0x00030016u,0x00000009u,0x00000020u,0x00040017u,0x0000000au,0x00000009u,0x00000004u,0x00050021u,0x0000000bu,0x0000000au,0x00000008u,0x00000008u,0x00050021u,0x00000010u,0x00000009u,0x00000008u,0x00000008u,0x00090019u,0x00000015u,0x00000009u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000001u,0x00000000u,0x0003001bu,0x00000016u,0x00000015u,0x00040020u,0x00000017u,0x00000000u,0x00000016u,0x0004003bu,0x00000017u,0x00000018u,0x00000000u,0x0004002bu,0x00000006u,0x0000001bu,0x00000000u,0x0005002cu,0x00000007u,0x0000001cu,0x0000001bu,0x0000001bu,0x0004002bu,0x00000006u,0x0000001eu,0x00000001u,0x0004003bu,0x00000017u,0x00000026u,0x00000000u,Line truncated
|
||||
static const size_t OF3_FLOWREG_LEN = 1936;
|
||||
static const uint32_t DDFG_SYNTH_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000208u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000001cu,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00050005u,0x0000000bu,0x616d756cu,0x33667628u,0x0000003bu,0x00030005u,0x0000000au,0x00000063u,0x00030005u,0x00000018u,0x00646967u,0x00080005u,0x0000001cu,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000021u,0x006d6964u,0x00040005u,0x00000024u,0x74754f75u,0x00000000u,0x00040005u,0x0000003eu,0x44766e69u,0x00006d69u,0x00030005u,0x00000044u,0x00007675u,0x00030005u,0x0000004du,0x00000055u,0x00060006u,0x0000004du,0x00000000u,0x776f6c66u,0x6c616353u,0x00000065u,0x00050006u,0x0000004du,0x00000001u,0x68706c61u,0x00000061u,0x00050006u,0x0000004du,0x00000002u,0x61746562u,0x00000000u,0x00050006u,0x0000004du,0x00000003u,0x626d616cu,0x00006164u,0x00050006u,0x0000004du,0x00000004u,0x69737065u,0x006e6f6cu,0x00060006u,0x0000004du,0x00000005u,0x746f6870u,0x6163536fu,0x0000656cu,0x00050006u,0x0000004du,0x00000006u,0x30646170u,0x00000000u,0x00050006u,0x0000004du,0x00000007u,0x31646170u,0x00000000u,0x00050006u,0x0000004du,0x00000008u,0x52647568u,0x00746365u,0x00030005u,0x0000004fu,0x00000000u,0x00040005u,0x0000008cu,0x72754375u,0x00000072u,0x00030005u,0x00000098u,0x00676264u,0x00030005u,0x0000009fu,0x00007366u,0x00040005u,0x000000a9u,0x64774273u,0x00000000u,0x00050005u,0x000000aau,0x6f6c4675u,0x64774277u,0x00000000u,0x00040005u,0x000000aeu,0x64774673u,0x00000000u,0x00050005u,0x000000afu,0x6f6c4675u,0x64774677u,0x00000000u,0x00030005u,0x000000b3u,0x00313046u,0x00030005u,0x000000bau,0x00303146u,0x00040005u,0x000000c1u,0x31306663u,0x00000000u,0x00040005u,0x000000c5u,0x30316663u,0x00000000u,0x00030005u,0x000000c9u,0x0031306du,0x00030005u,0x000000d5u,0x0030316du,0x00030005u,0x000000e0u,0x00004170u,0x00030005u,0x000000e8u,0x00004270u,0x00030005u,0x000000f0u,0x00003043u,0x00040005u,0x000000f1u,0x65725075u,0x00000076u,0x00030005u,0x000000f6u,0x00003143u,0x00030005u,0x000000fbu,0x00004165u,0x00040005u,0x000000fcu,0x61726170u,0x0000006du,0x00040005u,0x0000010cu,0x61726170u,0x0000006du,0x00030005u,0x00000111u,0x00004265u,0x00040005u,0x00000112u,0x61726170u,0x0000006du,0x00040005u,0x00000122u,0x61726170u,0x0000006du,0x00040005u,0x00000127u,0x76556266u,0x00000000u,0x00040005u,0x0000012bu,0x65526266u,0x00646973u,0x00040005u,0x00000138u,0x736e6f63u,0x00747369u,0x00030005u,0x00000149u,0x0000305au,0x00030005u,0x00000157u,0x0000315au,0x00040005u,0x00000163u,0x30756174u,0x00000000u,0x00040005u,0x00000167u,0x31756174u,0x00000000u,0x00040005u,0x0000016au,0x78616d7au,0x00000000u,0x00030005u,0x0000016eu,0x00003057u,0x00030005u,0x00000179u,0x00003157u,0x00040005u,0x00000183u,0x6d757357u,0x00000000u,0x00040005u,0x00000187u,0x70726177u,0x00006465u,0x00050005u,0x000001a0u,0x736f7263u,0x64616673u,0x00000065u,0x00050005u,0x000001adu,0x746f6870u,0x7272456fu,0x00000000u,0x00050005u,0x000001b1u,0x746f6870u,0x7572546fu,0x00007473u,0x00040005u,0x000001b7u,0x73757274u,0x00000074u,0x00040005u,0x000001c3u,0x76657270u,0x00000043u,0x00040005u,0x000001c8u,0x72727563u,0x00000043u,0x00050005u,0x000001cdu,0x746e6f63u,0x44746e65u,0x00666669u,0x00050005u,0x000001d2u,0x72746e75u,0x65747375u,0x00000064u,0x00040005u,0x000001d7u,0x44676962u,0x00666669u,0x00050005u,0x000001dcu,0x70616e73u,0x75436f54u,0x00007272u,0x00040005u,0x000001e5u,0x6374756fu,0x00000000u,0x00030005u,0x000001eeu,0x0067616du,0x00030005u,0x000001f2u,0x0000006eu,0x00030005u,0x000001f6u,0x00746173u,0x00040047u,0x0000001cu,0x0000000bu,0x0000001cu,0x00030047u,0x00000024u,0x00000019u,0x00040047u,0x00000024u,0x00000021u,0x00000030u,0x00040047u,0x00000024u,0x00000022u,0x00000000u,0x00030047u,0x0000004du,0x00000002u,0x00050048u,0x0000004du,0x00000000u,0x00000023u,0x00000000u,0x00050048u,0x0000004du,0x00000001u,0x00000023u,0x00000004u,0x00050048u,0x0000004du,0x00000002u,0x00000023u,0x00000008u,0x00050048u,0x0000004du,0x00000003u,0x00000023u,0x0000000cu,0x00050048u,0x0000004du,0x00000004u,0x00000023u,0x00000010u,0x00050048u,0x0000004du,0x00000005u,0x00000023u,0x00000014u,0x00050048u,0x0000004du,0x00000006u,0x00000023u,0x00000018u,0x00050048u,0x0000004du,0x00000007u,0x00000023u,0x0000001cu,0x00050048u,0x0000004du,0x00000008u,0x00000023u,0x00000020u,0x00040047u,0x0000004fu,0x00000021u,0x00000000u,0x00040047u,0x0000004fu,0x00000022u,0x00000000u,0x00040047u,0x0000008cu,0x00000021u,0x00000021u,0x00040047u,0x0000008cu,0x00000022u,0x00000000u,0x00040047u,0x000000aau,0x00000021u,0x00000023u,0x00040047u,0x000000aau,0x00000022u,0x00000000u,0x00040047u,0x000000afu,0x00000021u,0x00000022u,0x00040047u,0x000000afu,0x00000022u,0x00000000u,0x00040047u,0x000000f1u,0x00000021u,0x00000020u,0x00040047u,0x000000f1u,0Line truncated
|
||||
static const size_t DDFG_SYNTH_LEN = 3082;
|
||||
static const uint32_t WFG_SYNTH_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000208u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000001cu,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00050005u,0x0000000bu,0x616d756cu,0x33667628u,0x0000003bu,0x00030005u,0x0000000au,0x00000063u,0x00030005u,0x00000018u,0x00646967u,0x00080005u,0x0000001cu,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000021u,0x006d6964u,0x00040005u,0x00000024u,0x74754f75u,0x00000000u,0x00040005u,0x0000003eu,0x44766e69u,0x00006d69u,0x00030005u,0x00000044u,0x00007675u,0x00030005u,0x0000004du,0x00000055u,0x00060006u,0x0000004du,0x00000000u,0x776f6c66u,0x6c616353u,0x00000065u,0x00050006u,0x0000004du,0x00000001u,0x68706c61u,0x00000061u,0x00050006u,0x0000004du,0x00000002u,0x61746562u,0x00000000u,0x00050006u,0x0000004du,0x00000003u,0x626d616cu,0x00006164u,0x00050006u,0x0000004du,0x00000004u,0x69737065u,0x006e6f6cu,0x00060006u,0x0000004du,0x00000005u,0x746f6870u,0x6163536fu,0x0000656cu,0x00050006u,0x0000004du,0x00000006u,0x30646170u,0x00000000u,0x00050006u,0x0000004du,0x00000007u,0x31646170u,0x00000000u,0x00050006u,0x0000004du,0x00000008u,0x52647568u,0x00746365u,0x00030005u,0x0000004fu,0x00000000u,0x00040005u,0x0000008cu,0x72754375u,0x00000072u,0x00030005u,0x00000098u,0x00676264u,0x00030005u,0x0000009fu,0x00007366u,0x00040005u,0x000000a9u,0x64774273u,0x00000000u,0x00050005u,0x000000aau,0x6f6c4675u,0x64774277u,0x00000000u,0x00040005u,0x000000aeu,0x64774673u,0x00000000u,0x00050005u,0x000000afu,0x6f6c4675u,0x64774677u,0x00000000u,0x00030005u,0x000000b3u,0x00313046u,0x00030005u,0x000000bau,0x00303146u,0x00040005u,0x000000c1u,0x31306663u,0x00000000u,0x00040005u,0x000000c5u,0x30316663u,0x00000000u,0x00030005u,0x000000c9u,0x0031306du,0x00030005u,0x000000d5u,0x0030316du,0x00030005u,0x000000e0u,0x00004170u,0x00030005u,0x000000e8u,0x00004270u,0x00030005u,0x000000f0u,0x00003043u,0x00040005u,0x000000f1u,0x65725075u,0x00000076u,0x00030005u,0x000000f6u,0x00003143u,0x00030005u,0x000000fbu,0x00004165u,0x00040005u,0x000000fcu,0x61726170u,0x0000006du,0x00040005u,0x0000010cu,0x61726170u,0x0000006du,0x00030005u,0x00000111u,0x00004265u,0x00040005u,0x00000112u,0x61726170u,0x0000006du,0x00040005u,0x00000122u,0x61726170u,0x0000006du,0x00040005u,0x00000127u,0x76556266u,0x00000000u,0x00040005u,0x0000012bu,0x65526266u,0x00646973u,0x00040005u,0x00000138u,0x736e6f63u,0x00747369u,0x00030005u,0x00000149u,0x0000305au,0x00030005u,0x00000157u,0x0000315au,0x00040005u,0x00000163u,0x30756174u,0x00000000u,0x00040005u,0x00000167u,0x31756174u,0x00000000u,0x00040005u,0x0000016au,0x78616d7au,0x00000000u,0x00030005u,0x0000016eu,0x00003057u,0x00030005u,0x00000179u,0x00003157u,0x00040005u,0x00000183u,0x6d757357u,0x00000000u,0x00040005u,0x00000187u,0x70726177u,0x00006465u,0x00050005u,0x000001a0u,0x736f7263u,0x64616673u,0x00000065u,0x00050005u,0x000001adu,0x746f6870u,0x7272456fu,0x00000000u,0x00050005u,0x000001b1u,0x746f6870u,0x7572546fu,0x00007473u,0x00040005u,0x000001b7u,0x73757274u,0x00000074u,0x00040005u,0x000001c3u,0x76657270u,0x00000043u,0x00040005u,0x000001c8u,0x72727563u,0x00000043u,0x00050005u,0x000001cdu,0x746e6f63u,0x44746e65u,0x00666669u,0x00050005u,0x000001d2u,0x72746e75u,0x65747375u,0x00000064u,0x00040005u,0x000001d7u,0x44676962u,0x00666669u,0x00050005u,0x000001dcu,0x70616e73u,0x75436f54u,0x00007272u,0x00040005u,0x000001e5u,0x6374756fu,0x00000000u,0x00030005u,0x000001eeu,0x0067616du,0x00030005u,0x000001f2u,0x0000006eu,0x00030005u,0x000001f6u,0x00746173u,0x00040047u,0x0000001cu,0x0000000bu,0x0000001cu,0x00030047u,0x00000024u,0x00000019u,0x00040047u,0x00000024u,0x00000021u,0x00000030u,0x00040047u,0x00000024u,0x00000022u,0x00000000u,0x00030047u,0x0000004du,0x00000002u,0x00050048u,0x0000004du,0x00000000u,0x00000023u,0x00000000u,0x00050048u,0x0000004du,0x00000001u,0x00000023u,0x00000004u,0x00050048u,0x0000004du,0x00000002u,0x00000023u,0x00000008u,0x00050048u,0x0000004du,0x00000003u,0x00000023u,0x0000000cu,0x00050048u,0x0000004du,0x00000004u,0x00000023u,0x00000010u,0x00050048u,0x0000004du,0x00000005u,0x00000023u,0x00000014u,0x00050048u,0x0000004du,0x00000006u,0x00000023u,0x00000018u,0x00050048u,0x0000004du,0x00000007u,0x00000023u,0x0000001cu,0x00050048u,0x0000004du,0x00000008u,0x00000023u,0x00000020u,0x00040047u,0x0000004fu,0x00000021u,0x00000000u,0x00040047u,0x0000004fu,0x00000022u,0x00000000u,0x00040047u,0x0000008cu,0x00000021u,0x00000021u,0x00040047u,0x0000008cu,0x00000022u,0x00000000u,0x00040047u,0x000000aau,0x00000021u,0x00000023u,0x00040047u,0x000000aau,0x00000022u,0x00000000u,0x00040047u,0x000000afu,0x00000021u,0x00000022u,0x00040047u,0x000000afu,0x00000022u,0x00000000u,0x00040047u,0x000000f1u,0x00000021u,0x00000020u,0x00040047u,0x000000f1u,0xLine truncated
|
||||
static const size_t WFG_SYNTH_LEN = 3082;
|
||||
struct Blob { const uint32_t* code; size_t words; };
|
||||
enum Shader { OF3_LUMA, OF3_DOWNSAMPLE, OF3_FLOW, OF3_EXPAND, OF3_FLOW_M4, OF3_EXPAND_M4, OF3_GM_REDUCE, OF3_GM_PREWARP, OF3_FLOWREG, DDFG_SYNTH, SHADER_COUNT };
|
||||
enum Shader { OF3_LUMA, OF3_DOWNSAMPLE, OF3_FLOW, OF3_EXPAND, OF3_FLOW_M4, OF3_EXPAND_M4, OF3_GM_REDUCE, OF3_GM_PREWARP, OF3_FLOWREG, WFG_SYNTH, SHADER_COUNT };
|
||||
static inline Blob blob(Shader s) {
|
||||
switch (s) {
|
||||
case OF3_LUMA: return { OF3_LUMA_SPV, OF3_LUMA_LEN };
|
||||
@@ -35,7 +35,7 @@ static inline Blob blob(Shader s) {
|
||||
case OF3_GM_REDUCE: return { OF3_GM_REDUCE_SPV, OF3_GM_REDUCE_LEN };
|
||||
case OF3_GM_PREWARP: return { OF3_GM_PREWARP_SPV, OF3_GM_PREWARP_LEN };
|
||||
case OF3_FLOWREG: return { OF3_FLOWREG_SPV, OF3_FLOWREG_LEN };
|
||||
case DDFG_SYNTH: return { DDFG_SYNTH_SPV, DDFG_SYNTH_LEN };
|
||||
case WFG_SYNTH: return { WFG_SYNTH_SPV, WFG_SYNTH_LEN };
|
||||
default: return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
namespace ddfg {
|
||||
namespace winfg {
|
||||
|
||||
#define VKOK(x) do { if ((x) != VK_SUCCESS) { DDFG_LOGE("vk fail: %s", #x); return false; } } while (0)
|
||||
#define VKOK(x) do { if ((x) != VK_SUCCESS) { WFG_LOGE("vk fail: %s", #x); return false; } } while (0)
|
||||
|
||||
uint32_t FrameGen::findMemType(uint32_t bits, VkMemoryPropertyFlags props) const {
|
||||
for (uint32_t i = 0; i < memProps_.memoryTypeCount; ++i)
|
||||
@@ -153,9 +153,9 @@ bool FrameGen::init(const DeviceDispatch* dd, const InstanceDispatch* id,
|
||||
if (!makePipe(pGmReduce_, embedded::OF3_GM_REDUCE, unused)) return false;
|
||||
if (!makePipe(pGmPrewarp_, embedded::OF3_GM_PREWARP, unused)) return false;
|
||||
if (!makePipe(pFlowReg_, embedded::OF3_FLOWREG, unused)) return false;
|
||||
if (!makePipe(pSynth_, embedded::DDFG_SYNTH, unused)) return false;
|
||||
if (!makeGmBuffers()) { DDFG_LOGE("framegen: GM SSBO alloc failed - C1 disabled"); }
|
||||
DDFG_LOGI("framegen init ok (queueFamily=%u, 10 pipelines, model=%d, C1 gm=%d, C2 flow_reg=%d iters=%d)",
|
||||
if (!makePipe(pSynth_, embedded::WFG_SYNTH, unused)) return false;
|
||||
if (!makeGmBuffers()) { WFG_LOGE("framegen: GM SSBO alloc failed - C1 disabled"); }
|
||||
WFG_LOGI("framegen init ok (queueFamily=%u, 10 pipelines, model=%d, C1 gm=%d, C2 flow_reg=%d iters=%d)",
|
||||
queueFamily_, cfg_.model, cfg_.gmMode, cfg_.frMode, cfg_.frIters);
|
||||
return true;
|
||||
}
|
||||
@@ -194,7 +194,7 @@ bool FrameGen::onResize(VkExtent2D extent, VkFormat , bool force) {
|
||||
if (!makeImage(flowRegA_, levelExtent(extent, flowFinest_), VK_FORMAT_R16G16B16A16_SFLOAT, regUsage)) return false;
|
||||
if (!makeImage(flowRegB_, levelExtent(extent, flowFinest_), VK_FORMAT_R16G16B16A16_SFLOAT, regUsage)) return false;
|
||||
ready_ = true;
|
||||
DDFG_LOGI("framegen resized to %ux%u (%d pyramid levels, kFlowFinest=%d perf_preset=%d)",
|
||||
WFG_LOGI("framegen resized to %ux%u (%d pyramid levels, kFlowFinest=%d perf_preset=%d)",
|
||||
extent.width, extent.height, kLevels, flowFinest_, cfg_.perfPreset);
|
||||
return true;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "config.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace ddfg {
|
||||
namespace winfg {
|
||||
|
||||
class FrameGen {
|
||||
public:
|
||||
@@ -12,8 +12,7 @@ public:
|
||||
void configure(const Config& c);
|
||||
bool onResize(VkExtent2D extent, VkFormat colorFormat, bool force = false);
|
||||
void record(VkCommandBuffer cmd, VkImageView prevView, VkImageView currView,
|
||||
VkImageView outView, float alpha, uint32_t gmSlot = 0,
|
||||
uint32_t synthSlot = 0, bool reuseFlow = false);
|
||||
VkImageView outView, float alpha, uint32_t gmSlot = 0);
|
||||
void destroy();
|
||||
bool valid() const { return device_ != VK_NULL_HANDLE && ready_; }
|
||||
const Config& config() const { return cfg_; }
|
||||
@@ -26,7 +25,6 @@ public:
|
||||
|
||||
|
||||
static constexpr int kGmSlots = 3;
|
||||
static constexpr int kSynthSlots = 3;
|
||||
static constexpr int kGmLevel = 3;
|
||||
static constexpr int kGmThreads = 512;
|
||||
static constexpr int kGmStride = 32;
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#if defined(__ANDROID__)
|
||||
#include <android/log.h>
|
||||
#define WFG_LOGI(...) __android_log_print(ANDROID_LOG_INFO, "win-fg", __VA_ARGS__)
|
||||
#define WFG_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "win-fg", __VA_ARGS__)
|
||||
#else
|
||||
#include <cstdio>
|
||||
#define WFG_LOGI(...) do { std::fprintf(stderr, "[win-fg] " __VA_ARGS__); std::fprintf(stderr, "\n"); } while(0)
|
||||
#define WFG_LOGE(...) do { std::fprintf(stderr, "[win-fg][E] " __VA_ARGS__); std::fprintf(stderr, "\n"); } while(0)
|
||||
#endif
|
||||
|
||||
#define WFG_LOGD(cond, ...) do { if (cond) WFG_LOGI(__VA_ARGS__); } while(0)
|
||||
@@ -1,34 +1,31 @@
|
||||
|
||||
namespace {
|
||||
struct ScratchState {
|
||||
VkBuffer synthUbo[ddfg::FrameGen::kSynthSlots]{};
|
||||
VkDeviceMemory synthUboMem[ddfg::FrameGen::kSynthSlots]{};
|
||||
void* synthUboPtr[ddfg::FrameGen::kSynthSlots]{};
|
||||
VkBuffer synthUbo = VK_NULL_HANDLE; VkDeviceMemory synthUboMem = VK_NULL_HANDLE; void* synthUboPtr = nullptr;
|
||||
std::vector<VkBuffer> flowUbo; std::vector<VkDeviceMemory> flowUboMem; std::vector<void*> flowUboPtr;
|
||||
VkBuffer expandUbo = VK_NULL_HANDLE; VkDeviceMemory expandUboMem = VK_NULL_HANDLE; void* expandUboPtr = nullptr;
|
||||
VkBuffer flowRegUbo = VK_NULL_HANDLE; VkDeviceMemory flowRegUboMem = VK_NULL_HANDLE; void* flowRegUboPtr = nullptr;
|
||||
VkBuffer gmUbo[ddfg::FrameGen::kGmSlots]{};
|
||||
VkDeviceMemory gmUboMem[ddfg::FrameGen::kGmSlots]{};
|
||||
void* gmUboPtr[ddfg::FrameGen::kGmSlots]{};
|
||||
VkBuffer gmUbo[winfg::FrameGen::kGmSlots]{};
|
||||
VkDeviceMemory gmUboMem[winfg::FrameGen::kGmSlots]{};
|
||||
void* gmUboPtr[winfg::FrameGen::kGmSlots]{};
|
||||
VkDescriptorSet sLumaCurr = VK_NULL_HANDLE, sLumaPrev = VK_NULL_HANDLE;
|
||||
std::vector<VkDescriptorSet> sDownA, sDownB, sFlow;
|
||||
VkDescriptorSet sExpand[ddfg::FrameGen::kGmSlots]{};
|
||||
VkDescriptorSet sExpand[winfg::FrameGen::kGmSlots]{};
|
||||
|
||||
|
||||
|
||||
VkDescriptorSet sFlowReg0 = VK_NULL_HANDLE, sFlowRegAB = VK_NULL_HANDLE, sFlowRegBA = VK_NULL_HANDLE;
|
||||
VkDescriptorSet sSynth[ddfg::FrameGen::kSynthSlots]{};
|
||||
VkDescriptorSet sGmReduce[ddfg::FrameGen::kGmSlots]{};
|
||||
std::vector<VkDescriptorSet> sGmPrewarp[ddfg::FrameGen::kGmSlots];
|
||||
VkDescriptorSet sSynth = VK_NULL_HANDLE;
|
||||
VkDescriptorSet sGmReduce[winfg::FrameGen::kGmSlots]{};
|
||||
std::vector<VkDescriptorSet> sGmPrewarp[winfg::FrameGen::kGmSlots];
|
||||
VkExtent2D builtFor{0,0};
|
||||
bool built = false;
|
||||
bool flowInited = false;
|
||||
bool flowReady = false;
|
||||
};
|
||||
ScratchState g_scratch;
|
||||
}
|
||||
|
||||
static bool makeUbo(const ddfg::DeviceDispatch* dd, VkDevice dev,
|
||||
static bool makeUbo(const winfg::DeviceDispatch* dd, VkDevice dev,
|
||||
const VkPhysicalDeviceMemoryProperties& mp,
|
||||
VkDeviceSize size, VkBuffer& buf, VkDeviceMemory& mem, void*& ptr) {
|
||||
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
@@ -44,21 +41,21 @@ static bool makeUbo(const ddfg::DeviceDispatch* dd, VkDevice dev,
|
||||
return dd->MapMemory(dev, mem, 0, size, 0, &ptr) == VK_SUCCESS;
|
||||
}
|
||||
|
||||
static VkDescriptorSet allocSet(const ddfg::DeviceDispatch* dd, VkDevice dev,
|
||||
static VkDescriptorSet allocSet(const winfg::DeviceDispatch* dd, VkDevice dev,
|
||||
VkDescriptorPool pool, VkDescriptorSetLayout layout) {
|
||||
VkDescriptorSetAllocateInfo ai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
||||
ai.descriptorPool = pool; ai.descriptorSetCount = 1; ai.pSetLayouts = &layout;
|
||||
VkDescriptorSet s = VK_NULL_HANDLE; dd->AllocateDescriptorSets(dev, &ai, &s); return s;
|
||||
}
|
||||
|
||||
static void wImg(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
|
||||
static void wImg(const winfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
|
||||
VkDescriptorType type, VkImageView view, VkSampler samp, VkImageLayout layout) {
|
||||
VkDescriptorImageInfo ii{}; ii.imageView = view; ii.sampler = samp; ii.imageLayout = layout;
|
||||
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
w.dstSet = set; w.dstBinding = binding; w.descriptorCount = 1; w.descriptorType = type; w.pImageInfo = ⅈ
|
||||
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
|
||||
}
|
||||
static void wUbo(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
|
||||
static void wUbo(const winfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
|
||||
VkBuffer buf, VkDeviceSize range) {
|
||||
VkDescriptorBufferInfo bi{}; bi.buffer = buf; bi.range = range;
|
||||
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
@@ -66,7 +63,7 @@ static void wUbo(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet s
|
||||
w.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; w.pBufferInfo = &bi;
|
||||
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
|
||||
}
|
||||
static void wSsbo(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
|
||||
static void wSsbo(const winfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
|
||||
VkBuffer buf, VkDeviceSize range) {
|
||||
VkDescriptorBufferInfo bi{}; bi.buffer = buf; bi.range = range;
|
||||
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
@@ -120,7 +117,7 @@ static void mulAff(const double A[3][3], const double B[3][3], double out[3][3])
|
||||
out[2][0]=0.0; out[2][1]=0.0; out[2][2]=1.0;
|
||||
}
|
||||
|
||||
namespace ddfg {
|
||||
namespace winfg {
|
||||
|
||||
static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
const VkPhysicalDeviceMemoryProperties& mp, VkDescriptorPool pool,
|
||||
@@ -143,7 +140,7 @@ static void destroyScratch(const DeviceDispatch* dd, VkDevice dev, VkDescriptorP
|
||||
b = VK_NULL_HANDLE; m = VK_NULL_HANDLE; p = nullptr;
|
||||
};
|
||||
auto& S = g_scratch;
|
||||
for (int s = 0; s < FrameGen::kSynthSlots; ++s) freeUbo(S.synthUbo[s], S.synthUboMem[s], S.synthUboPtr[s]);
|
||||
freeUbo(S.synthUbo, S.synthUboMem, S.synthUboPtr);
|
||||
freeUbo(S.expandUbo, S.expandUboMem, S.expandUboPtr);
|
||||
freeUbo(S.flowRegUbo, S.flowRegUboMem, S.flowRegUboPtr);
|
||||
for (size_t i = 0; i < S.flowUbo.size(); ++i) freeUbo(S.flowUbo[i], S.flowUboMem[i], S.flowUboPtr[i]);
|
||||
@@ -157,7 +154,7 @@ void FrameGen::configure(const Config& c) {
|
||||
int want = flowFinestForPreset(cfg_.perfPreset);
|
||||
const int maxFinest = kLevels - 2;
|
||||
if (want > maxFinest) {
|
||||
DDFG_LOGE("perf-preset=%d wants kFlowFinest=%d but only %d pyramid levels exist "
|
||||
WFG_LOGE("perf-preset=%d wants kFlowFinest=%d but only %d pyramid levels exist "
|
||||
"(need >=2 solved) - clamping to %d", cfg_.perfPreset, want, kLevels, maxFinest);
|
||||
want = maxFinest;
|
||||
}
|
||||
@@ -166,7 +163,7 @@ void FrameGen::configure(const Config& c) {
|
||||
const int prevFinest = flowFinest_;
|
||||
flowFinest_ = want;
|
||||
const bool live = (flowFinest_ != prevFinest) && ready_;
|
||||
DDFG_LOGI("perf-preset=%d -> kFlowFinest=%d (was %d) live-rebuild=%d ready=%d",
|
||||
WFG_LOGI("perf-preset=%d -> kFlowFinest=%d (was %d) live-rebuild=%d ready=%d",
|
||||
cfg_.perfPreset, flowFinest_, prevFinest, live ? 1 : 0, ready_ ? 1 : 0);
|
||||
|
||||
if (!live) return;
|
||||
@@ -177,12 +174,12 @@ void FrameGen::configure(const Config& c) {
|
||||
dd_->DeviceWaitIdle(device_);
|
||||
destroyScratch(dd_, device_, descPool_);
|
||||
if (!onResize(extent_, VK_FORMAT_UNDEFINED, true))
|
||||
DDFG_LOGE("perf-preset live rebuild: onResize FAILED at %ux%u - FG disabled until next resize",
|
||||
WFG_LOGE("perf-preset live rebuild: onResize FAILED at %ux%u - FG disabled until next resize",
|
||||
extent_.width, extent_.height);
|
||||
for (int i = 0; i < 6; ++i) gmRun_[i] = 0.0;
|
||||
for (int s = 0; s < kGmSlots; ++s) { gmSlotValid_[s] = false; for (int i = 0; i < 6; ++i) gmLinAt_[s][i] = 0.0; }
|
||||
gmHaveEst_ = false; gmEngaged_ = false; gmStableCount_ = 0; gmLastTransPx_ = 0.0f;
|
||||
DDFG_LOGI("perf-preset live rebuild DONE (kFlowFinest=%d, flow predictor reset)", flowFinest_);
|
||||
WFG_LOGI("perf-preset live rebuild DONE (kFlowFinest=%d, flow predictor reset)", flowFinest_);
|
||||
}
|
||||
|
||||
bool FrameGen::updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent) {
|
||||
@@ -252,10 +249,9 @@ bool FrameGen::updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent) {
|
||||
}
|
||||
|
||||
void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView currView,
|
||||
VkImageView outView, float alpha, uint32_t gmSlot, uint32_t synthSlot, bool reuseFlow) {
|
||||
VkImageView outView, float alpha, uint32_t gmSlot) {
|
||||
if (!valid()) return;
|
||||
if (gmSlot >= (uint32_t)kGmSlots) gmSlot = 0;
|
||||
if (synthSlot >= (uint32_t)kSynthSlots) synthSlot = 0;
|
||||
const bool m4 = (cfg_.model == 4);
|
||||
const Pipe& flowP = m4 ? pFlowM4_ : pFlow_;
|
||||
const Pipe& expandP = m4 ? pExpandM4_ : pExpand_;
|
||||
@@ -267,42 +263,35 @@ void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView cur
|
||||
pFlowReg_, flowRegA_, flowRegB_, gmSsbo_, kLevels, cfg_);
|
||||
}
|
||||
|
||||
// Every generation of one real frame is recorded into the same command buffer, so the flow
|
||||
// between prev and curr is computed once and each generation gets its own synth UBO and set.
|
||||
if (reuseFlow && !g_scratch.flowReady) return;
|
||||
if (!reuseFlow) {
|
||||
bool gmEngaged = updateGlobalMotion(gmSlot, extent_);
|
||||
if (g_scratch.gmUboPtr[gmSlot]) {
|
||||
GMUBO g{};
|
||||
g.lin[0]=(float)gmRun_[0]; g.lin[1]=(float)gmRun_[1]; g.lin[2]=(float)gmRun_[2]; g.lin[3]=(float)gmRun_[3];
|
||||
g.linT[0]=(float)gmRun_[4]; g.linT[1]=(float)gmRun_[5]; g.linT[2]=gmEngaged?1.0f:0.0f; g.linT[3]=0.0f;
|
||||
if (gmEngaged) {
|
||||
g.app[0]=(float)gmRun_[0]; g.app[1]=(float)gmRun_[1]; g.app[2]=(float)gmRun_[2]; g.app[3]=(float)gmRun_[3];
|
||||
g.appT[0]=(float)gmRun_[4]; g.appT[1]=(float)gmRun_[5];
|
||||
}
|
||||
std::memcpy(g_scratch.gmUboPtr[gmSlot], &g, sizeof(g));
|
||||
bool gmEngaged = updateGlobalMotion(gmSlot, extent_);
|
||||
if (g_scratch.gmUboPtr[gmSlot]) {
|
||||
GMUBO g{};
|
||||
g.lin[0]=(float)gmRun_[0]; g.lin[1]=(float)gmRun_[1]; g.lin[2]=(float)gmRun_[2]; g.lin[3]=(float)gmRun_[3];
|
||||
g.linT[0]=(float)gmRun_[4]; g.linT[1]=(float)gmRun_[5]; g.linT[2]=gmEngaged?1.0f:0.0f; g.linT[3]=0.0f;
|
||||
if (gmEngaged) {
|
||||
g.app[0]=(float)gmRun_[0]; g.app[1]=(float)gmRun_[1]; g.app[2]=(float)gmRun_[2]; g.app[3]=(float)gmRun_[3];
|
||||
g.appT[0]=(float)gmRun_[4]; g.appT[1]=(float)gmRun_[5];
|
||||
}
|
||||
{
|
||||
static int lastEng = -1;
|
||||
if (gmEngaged != (lastEng == 1) || (gmFrames_ % 300ull) == 0) {
|
||||
DDFG_LOGI("C1 global-motion: engaged=%d mode=%d stable=%d transPx=%.1f (model=%d)",
|
||||
gmEngaged ? 1 : 0, cfg_.gmMode, gmStableCount_, gmLastTransPx_, cfg_.model);
|
||||
lastEng = gmEngaged ? 1 : 0;
|
||||
}
|
||||
std::memcpy(g_scratch.gmUboPtr[gmSlot], &g, sizeof(g));
|
||||
}
|
||||
{
|
||||
static int lastEng = -1;
|
||||
if (gmEngaged != (lastEng == 1) || (gmFrames_ % 300ull) == 0) {
|
||||
WFG_LOGI("C1 global-motion: engaged=%d mode=%d stable=%d transPx=%.1f (model=%d)",
|
||||
gmEngaged ? 1 : 0, cfg_.gmMode, gmStableCount_, gmLastTransPx_, cfg_.model);
|
||||
lastEng = gmEngaged ? 1 : 0;
|
||||
}
|
||||
|
||||
wImg(dd_, device_, g_scratch.sLumaCurr, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, g_scratch.sLumaPrev, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
}
|
||||
|
||||
SynthUBO su{cfg_.flowScale, alpha, cfg_.beta, cfg_.lambda, cfg_.epsilon, cfg_.photoScale, 0, 0,
|
||||
cfg_.hudX0, cfg_.hudY0, cfg_.hudX1, cfg_.hudY1};
|
||||
if (g_scratch.synthUboPtr[synthSlot]) std::memcpy(g_scratch.synthUboPtr[synthSlot], &su, sizeof(su));
|
||||
if (g_scratch.synthUboPtr) std::memcpy(g_scratch.synthUboPtr, &su, sizeof(su));
|
||||
|
||||
const VkDescriptorSet synthSet = g_scratch.sSynth[synthSlot];
|
||||
wImg(dd_, device_, synthSet, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, synthSet, 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, synthSet, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outView, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL);
|
||||
wImg(dd_, device_, g_scratch.sLumaCurr, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, g_scratch.sLumaPrev, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, g_scratch.sSynth, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, g_scratch.sSynth, 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
wImg(dd_, device_, g_scratch.sSynth, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outView, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL);
|
||||
|
||||
auto disp = [&](const Pipe& p, VkDescriptorSet set, VkExtent2D e) {
|
||||
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, p.pipeline);
|
||||
@@ -311,109 +300,106 @@ void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView cur
|
||||
};
|
||||
auto bar = [&](FrameGen::Img& i){ barrier(cmd, i.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL); };
|
||||
|
||||
if (!reuseFlow) {
|
||||
for (auto& i : pyrA_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
for (auto& i : pyrB_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
for (int l = flowFinest_; l < kLevels; ++l) barrier(cmd, pyrAs_[l].image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowExpA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowExpB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowRegA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowRegB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
{
|
||||
VkClearColorValue zero{};
|
||||
VkImageSubresourceRange rng{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
for (int l = flowFinest_; l < kLevels; ++l) {
|
||||
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
b.srcAccessMask = 0; b.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
b.image = flowLvl_[l].image; b.subresourceRange = rng;
|
||||
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||
dd_->CmdClearColorImage(cmd, flowLvl_[l].image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &zero, 1, &rng);
|
||||
b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||
}
|
||||
}
|
||||
|
||||
disp(pLuma_, g_scratch.sLumaCurr, levelExtent(extent_, 0));
|
||||
disp(pLuma_, g_scratch.sLumaPrev, levelExtent(extent_, 0));
|
||||
bar(pyrA_[0]); bar(pyrB_[0]);
|
||||
for (int l = 1; l < kLevels; ++l) {
|
||||
disp(pDown_, g_scratch.sDownA[l - 1], levelExtent(extent_, l));
|
||||
disp(pDown_, g_scratch.sDownB[l - 1], levelExtent(extent_, l));
|
||||
bar(pyrA_[l]); bar(pyrB_[l]);
|
||||
}
|
||||
if (gmBuffersReady_ && g_scratch.sGmReduce[gmSlot]) {
|
||||
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.pipeline);
|
||||
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.layout, 0, 1, &g_scratch.sGmReduce[gmSlot], 0, nullptr);
|
||||
dd_->CmdDispatch(cmd, (uint32_t)(kGmThreads / 64), 1, 1);
|
||||
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
mb.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; mb.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT,
|
||||
0, 1, &mb, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
for (auto& i : pyrA_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
for (auto& i : pyrB_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
for (int l = flowFinest_; l < kLevels; ++l) barrier(cmd, pyrAs_[l].image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowExpA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowExpB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowRegA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(cmd, flowRegB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
{
|
||||
VkClearColorValue zero{};
|
||||
VkImageSubresourceRange rng{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
for (int l = flowFinest_; l < kLevels; ++l) {
|
||||
disp(pGmPrewarp_, g_scratch.sGmPrewarp[gmSlot][l], levelExtent(extent_, l));
|
||||
bar(pyrAs_[l]);
|
||||
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
b.srcAccessMask = 0; b.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
b.image = flowLvl_[l].image; b.subresourceRange = rng;
|
||||
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||
dd_->CmdClearColorImage(cmd, flowLvl_[l].image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &zero, 1, &rng);
|
||||
b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||
}
|
||||
for (int l = kLevels - 1; l >= flowFinest_; --l) {
|
||||
disp(flowP, g_scratch.sFlow[l], levelExtent(extent_, l));
|
||||
bar(flowLvl_[l]);
|
||||
}
|
||||
|
||||
const bool frEngaged = (cfg_.frMode != 2) && (cfg_.frIters > 0);
|
||||
if (frEngaged) {
|
||||
const int F = flowFinest_;
|
||||
const VkExtent2D fe = levelExtent(extent_, F);
|
||||
if (g_scratch.flowRegUboPtr) {
|
||||
FlowRegUBO fu{cfg_.frDt, cfg_.frLambda, cfg_.frEdge, cfg_.frEps};
|
||||
std::memcpy(g_scratch.flowRegUboPtr, &fu, sizeof(fu));
|
||||
}
|
||||
FrameGen::Img* last = nullptr;
|
||||
for (int it = 0; it < cfg_.frIters; ++it) {
|
||||
VkDescriptorSet set; FrameGen::Img* outImg;
|
||||
if (it == 0) { set = g_scratch.sFlowReg0; outImg = &flowRegA_; }
|
||||
else if (it % 2 == 1) { set = g_scratch.sFlowRegAB; outImg = &flowRegB_; }
|
||||
else { set = g_scratch.sFlowRegBA; outImg = &flowRegA_; }
|
||||
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pFlowReg_.pipeline);
|
||||
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pFlowReg_.layout, 0, 1, &set, 0, nullptr);
|
||||
dd_->CmdDispatch(cmd, (fe.width + 15) / 16, (fe.height + 15) / 16, 1);
|
||||
bar(*outImg);
|
||||
last = outImg;
|
||||
}
|
||||
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
mb.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
mb.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0, 1, &mb, 0, nullptr, 0, nullptr);
|
||||
VkImageCopy region{};
|
||||
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
region.extent = {fe.width, fe.height, 1};
|
||||
dd_->CmdCopyImage(cmd, last->image, VK_IMAGE_LAYOUT_GENERAL,
|
||||
flowLvl_[F].image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion);
|
||||
VkMemoryBarrier mb2{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
mb2.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
mb2.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0, 1, &mb2, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
{
|
||||
static int lastFr = -1;
|
||||
int fe = frEngaged ? 1 : 0;
|
||||
if (fe != lastFr || (gmFrames_ % 300ull) == 0) {
|
||||
DDFG_LOGI("C2 flow-reg: engaged=%d mode=%d iters=%d lambda=%.2f dt=%.2f edge=%.1f eps=%.3f (model=%d)",
|
||||
fe, cfg_.frMode, cfg_.frIters, cfg_.frLambda, cfg_.frDt, cfg_.frEdge, cfg_.frEps, cfg_.model);
|
||||
lastFr = fe;
|
||||
}
|
||||
}
|
||||
|
||||
disp(expandP, g_scratch.sExpand[gmSlot], levelExtent(extent_, 0));
|
||||
bar(flowExpA_); bar(flowExpB_);
|
||||
g_scratch.flowReady = true;
|
||||
}
|
||||
disp(pSynth_, synthSet, levelExtent(extent_, 0));
|
||||
|
||||
disp(pLuma_, g_scratch.sLumaCurr, levelExtent(extent_, 0));
|
||||
disp(pLuma_, g_scratch.sLumaPrev, levelExtent(extent_, 0));
|
||||
bar(pyrA_[0]); bar(pyrB_[0]);
|
||||
for (int l = 1; l < kLevels; ++l) {
|
||||
disp(pDown_, g_scratch.sDownA[l - 1], levelExtent(extent_, l));
|
||||
disp(pDown_, g_scratch.sDownB[l - 1], levelExtent(extent_, l));
|
||||
bar(pyrA_[l]); bar(pyrB_[l]);
|
||||
}
|
||||
if (gmBuffersReady_ && g_scratch.sGmReduce[gmSlot]) {
|
||||
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.pipeline);
|
||||
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.layout, 0, 1, &g_scratch.sGmReduce[gmSlot], 0, nullptr);
|
||||
dd_->CmdDispatch(cmd, (uint32_t)(kGmThreads / 64), 1, 1);
|
||||
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
mb.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; mb.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT,
|
||||
0, 1, &mb, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
for (int l = flowFinest_; l < kLevels; ++l) {
|
||||
disp(pGmPrewarp_, g_scratch.sGmPrewarp[gmSlot][l], levelExtent(extent_, l));
|
||||
bar(pyrAs_[l]);
|
||||
}
|
||||
for (int l = kLevels - 1; l >= flowFinest_; --l) {
|
||||
disp(flowP, g_scratch.sFlow[l], levelExtent(extent_, l));
|
||||
bar(flowLvl_[l]);
|
||||
}
|
||||
|
||||
const bool frEngaged = (cfg_.frMode != 2) && (cfg_.frIters > 0);
|
||||
if (frEngaged) {
|
||||
const int F = flowFinest_;
|
||||
const VkExtent2D fe = levelExtent(extent_, F);
|
||||
if (g_scratch.flowRegUboPtr) {
|
||||
FlowRegUBO fu{cfg_.frDt, cfg_.frLambda, cfg_.frEdge, cfg_.frEps};
|
||||
std::memcpy(g_scratch.flowRegUboPtr, &fu, sizeof(fu));
|
||||
}
|
||||
FrameGen::Img* last = nullptr;
|
||||
for (int it = 0; it < cfg_.frIters; ++it) {
|
||||
VkDescriptorSet set; FrameGen::Img* outImg;
|
||||
if (it == 0) { set = g_scratch.sFlowReg0; outImg = &flowRegA_; }
|
||||
else if (it % 2 == 1) { set = g_scratch.sFlowRegAB; outImg = &flowRegB_; }
|
||||
else { set = g_scratch.sFlowRegBA; outImg = &flowRegA_; }
|
||||
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pFlowReg_.pipeline);
|
||||
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pFlowReg_.layout, 0, 1, &set, 0, nullptr);
|
||||
dd_->CmdDispatch(cmd, (fe.width + 15) / 16, (fe.height + 15) / 16, 1);
|
||||
bar(*outImg);
|
||||
last = outImg;
|
||||
}
|
||||
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
mb.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
mb.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0, 1, &mb, 0, nullptr, 0, nullptr);
|
||||
VkImageCopy region{};
|
||||
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
region.extent = {fe.width, fe.height, 1};
|
||||
dd_->CmdCopyImage(cmd, last->image, VK_IMAGE_LAYOUT_GENERAL,
|
||||
flowLvl_[F].image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion);
|
||||
VkMemoryBarrier mb2{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
mb2.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
mb2.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0, 1, &mb2, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
{
|
||||
static int lastFr = -1;
|
||||
int fe = frEngaged ? 1 : 0;
|
||||
if (fe != lastFr || (gmFrames_ % 300ull) == 0) {
|
||||
WFG_LOGI("C2 flow-reg: engaged=%d mode=%d iters=%d lambda=%.2f dt=%.2f edge=%.1f eps=%.3f (model=%d)",
|
||||
fe, cfg_.frMode, cfg_.frIters, cfg_.frLambda, cfg_.frDt, cfg_.frEdge, cfg_.frEps, cfg_.model);
|
||||
lastFr = fe;
|
||||
}
|
||||
}
|
||||
|
||||
disp(expandP, g_scratch.sExpand[gmSlot], levelExtent(extent_, 0));
|
||||
bar(flowExpA_); bar(flowExpB_);
|
||||
disp(pSynth_, g_scratch.sSynth, levelExtent(extent_, 0));
|
||||
}
|
||||
|
||||
static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
@@ -432,8 +418,7 @@ static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
S = ScratchState{};
|
||||
const auto GEN = VK_IMAGE_LAYOUT_GENERAL;
|
||||
|
||||
for (int s = 0; s < FrameGen::kSynthSlots; ++s)
|
||||
makeUbo(dd, dev, mp, sizeof(SynthUBO), S.synthUbo[s], S.synthUboMem[s], S.synthUboPtr[s]);
|
||||
makeUbo(dd, dev, mp, sizeof(SynthUBO), S.synthUbo, S.synthUboMem, S.synthUboPtr);
|
||||
makeUbo(dd, dev, mp, sizeof(FlowUBO), S.expandUbo, S.expandUboMem, S.expandUboPtr);
|
||||
if (S.expandUboPtr) { FlowUBO u{cfg.flowScale, 0, 0.6f, 1.6f}; std::memcpy(S.expandUboPtr, &u, sizeof(u)); }
|
||||
makeUbo(dd, dev, mp, sizeof(FlowRegUBO), S.flowRegUbo, S.flowRegUboMem, S.flowRegUboPtr);
|
||||
@@ -524,12 +509,10 @@ static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
|
||||
wImg(dd, dev, S.sFlowRegBA, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegA.view, VK_NULL_HANDLE, GEN);
|
||||
}
|
||||
|
||||
for (int s = 0; s < FrameGen::kSynthSlots; ++s) {
|
||||
S.sSynth[s] = allocSet(dd, dev, pool, pSynth.setLayout);
|
||||
wUbo(dd, dev, S.sSynth[s], 0, S.synthUbo[s], sizeof(SynthUBO));
|
||||
wImg(dd, dev, S.sSynth[s], 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expA.view, samp, GEN);
|
||||
wImg(dd, dev, S.sSynth[s], 35, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expB.view, samp, GEN);
|
||||
}
|
||||
S.sSynth = allocSet(dd, dev, pool, pSynth.setLayout);
|
||||
wUbo(dd, dev, S.sSynth, 0, S.synthUbo, sizeof(SynthUBO));
|
||||
wImg(dd, dev, S.sSynth, 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expA.view, samp, GEN);
|
||||
wImg(dd, dev, S.sSynth, 35, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expB.view, samp, GEN);
|
||||
|
||||
S.builtFor = ext; S.built = true;
|
||||
return true;
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <vulkan/vk_layer.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace ddfg {
|
||||
namespace winfg {
|
||||
|
||||
static inline void* dispatch_key(void* handle) { return *reinterpret_cast<void**>(handle); }
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "ddfg_engine.h"
|
||||
#include "ddfg_vkd.h"
|
||||
#include "winfg_engine.h"
|
||||
#include "winfg_vkd.h"
|
||||
#include "log.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace ddfg {
|
||||
namespace winfg {
|
||||
namespace {
|
||||
|
||||
float alphaFor(uint32_t k, uint32_t n) {
|
||||
@@ -31,18 +31,18 @@ Engine::~Engine() { destroyRing(); fg_.destroy(); }
|
||||
|
||||
bool Engine::init(const VkTable& table, VkPhysicalDevice phys, VkDevice dev,
|
||||
uint32_t queueFamily, VkQueue queue) {
|
||||
if (!ddfgVkdInit(table, dd_, id_)) {
|
||||
DDFG_LOGE("native: dispatch incomplete; frame generation unavailable");
|
||||
if (!winfgVkdInit(table, dd_, id_)) {
|
||||
WFG_LOGE("native: dispatch incomplete; frame generation unavailable");
|
||||
return false;
|
||||
}
|
||||
phys_ = phys; dev_ = dev; qf_ = queueFamily; queue_ = queue;
|
||||
id_.GetPhysicalDeviceMemoryProperties(phys_, &memProps_);
|
||||
if (!fg_.init(&dd_, &id_, phys_, dev_, qf_, queue_)) {
|
||||
DDFG_LOGE("native: FrameGen::init failed");
|
||||
WFG_LOGE("native: FrameGen::init failed");
|
||||
unavailable_ = true;
|
||||
return false;
|
||||
}
|
||||
DDFG_LOGI("native: chain ready (10 pipelines, embedded shaders)");
|
||||
WFG_LOGI("native: chain ready (10 pipelines, embedded shaders)");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ bool Engine::prepare(uint32_t w, uint32_t h, VkFormat fmt) {
|
||||
|
||||
|
||||
if (!fg_.onResize(VkExtent2D{w, h}, fmt, false)) {
|
||||
DDFG_LOGE("native: onResize %ux%u failed; frame generation unavailable", w, h);
|
||||
WFG_LOGE("native: onResize %ux%u failed; frame generation unavailable", w, h);
|
||||
unavailable_ = true;
|
||||
return false;
|
||||
}
|
||||
@@ -71,13 +71,11 @@ bool Engine::prepare(uint32_t w, uint32_t h, VkFormat fmt) {
|
||||
}
|
||||
builtW_ = w; builtH_ = h; builtFmt_ = fmt;
|
||||
history_ = 0; curr_ = 0; frames_ = 0;
|
||||
DDFG_LOGI("native: built %ux%u fmt=%d model=%d preset=%d flow=%.2f",
|
||||
WFG_LOGI("native: built %ux%u fmt=%d model=%d preset=%d flow=%.2f",
|
||||
w, h, (int)fmt, cfg_.model, cfg_.perfPreset, (double)cfg_.flowScale);
|
||||
return true;
|
||||
}
|
||||
|
||||
static_assert(kMaxGenerations <= (uint32_t)FrameGen::kSynthSlots, "each generation needs its own synth slot");
|
||||
|
||||
uint32_t Engine::plan(uint32_t capacity) const {
|
||||
if (!valid() || history_ < 2) return 0;
|
||||
const uint32_t want = (uint32_t)std::max(cfg_.multiplier - 1, 0);
|
||||
@@ -133,8 +131,7 @@ void Engine::generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t cou
|
||||
dd_.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toWrite);
|
||||
|
||||
fg_.record(cmd, prev.view, curr.view, targetView, alphaFor(generation, count), gmSlot_,
|
||||
generation, generation > 0);
|
||||
fg_.record(cmd, prev.view, curr.view, targetView, alphaFor(generation, count), gmSlot_);
|
||||
if (generation + 1 == count) gmSlot_ = (gmSlot_ + 1) % (uint32_t)FrameGen::kGmSlots;
|
||||
|
||||
(void)w; (void)h;
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
struct VkTable;
|
||||
|
||||
namespace ddfg {
|
||||
namespace winfg {
|
||||
|
||||
constexpr uint32_t kMaxGenerations = 3;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#include "ddfg_vkd.h"
|
||||
#include "winfg_vkd.h"
|
||||
#include "../VulkanRendererContext.h"
|
||||
#include <android/log.h>
|
||||
|
||||
bool ddfgVkdInit(const VkTable& table, ddfg::DeviceDispatch& dd, ddfg::InstanceDispatch& id) {
|
||||
bool winfgVkdInit(const VkTable& table, winfg::DeviceDispatch& dd, winfg::InstanceDispatch& id) {
|
||||
bool ok = true;
|
||||
#define COPY(dst, fn) \
|
||||
do { \
|
||||
dst.fn = table.fn; \
|
||||
if (!dst.fn) { \
|
||||
__android_log_print(ANDROID_LOG_ERROR, "DdFgVkd", \
|
||||
__android_log_print(ANDROID_LOG_ERROR, "WinFgVkd", \
|
||||
"entry point vk" #fn " did not resolve"); \
|
||||
ok = false; \
|
||||
} \
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "vk_dispatch.hpp"
|
||||
struct VkTable;
|
||||
bool winfgVkdInit(const VkTable& table, winfg::DeviceDispatch& dd, winfg::InstanceDispatch& id);
|
||||
@@ -43,10 +43,10 @@ target_include_directories(droiddeckwayland PRIVATE
|
||||
)
|
||||
|
||||
set(DROIDDECK_FRAMEGEN_LSFG_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../framegen/lsfg)
|
||||
set(DROIDDECK_FRAMEGEN_DDFG_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../framegen/ddfg)
|
||||
set(DROIDDECK_FRAMEGEN_WINFG_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../framegen/winfg)
|
||||
if (TARGET dxbc AND EXISTS ${DROIDDECK_FRAMEGEN_LSFG_DIR}/lsfg_engine.cpp
|
||||
AND EXISTS ${DROIDDECK_FRAMEGEN_DDFG_DIR}/ddfg_engine.cpp)
|
||||
message(STATUS "droiddeckwayland: frame-generation engines built in (LSFG Native, DD-FG Native)")
|
||||
AND EXISTS ${DROIDDECK_FRAMEGEN_WINFG_DIR}/winfg_engine.cpp)
|
||||
message(STATUS "droiddeckwayland: frame-generation engines built in (LSFG Native, Win-FG Native)")
|
||||
target_sources(droiddeckwayland PRIVATE
|
||||
src/framegen_engine.cpp
|
||||
${DROIDDECK_FRAMEGEN_LSFG_DIR}/lsfg_probe.cpp
|
||||
@@ -66,9 +66,9 @@ if (TARGET dxbc AND EXISTS ${DROIDDECK_FRAMEGEN_LSFG_DIR}/lsfg_engine.cpp
|
||||
${DROIDDECK_FRAMEGEN_LSFG_DIR}/lsfg_engine.cpp
|
||||
${DROIDDECK_FRAMEGEN_LSFG_DIR}/lsfg_governor.cpp
|
||||
${DROIDDECK_FRAMEGEN_LSFG_DIR}/lsfg_jni.cpp
|
||||
${DROIDDECK_FRAMEGEN_DDFG_DIR}/framegen.cpp
|
||||
${DROIDDECK_FRAMEGEN_DDFG_DIR}/ddfg_vkd.cpp
|
||||
${DROIDDECK_FRAMEGEN_DDFG_DIR}/ddfg_engine.cpp
|
||||
${DROIDDECK_FRAMEGEN_WINFG_DIR}/framegen.cpp
|
||||
${DROIDDECK_FRAMEGEN_WINFG_DIR}/winfg_vkd.cpp
|
||||
${DROIDDECK_FRAMEGEN_WINFG_DIR}/winfg_engine.cpp
|
||||
)
|
||||
target_include_directories(droiddeckwayland PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
@@ -77,8 +77,8 @@ if (TARGET dxbc AND EXISTS ${DROIDDECK_FRAMEGEN_LSFG_DIR}/lsfg_engine.cpp
|
||||
target_compile_options(droiddeckwayland PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-std=c++17 -fvisibility=hidden>)
|
||||
target_compile_definitions(droiddeckwayland PRIVATE
|
||||
DDFG_UPSTREAM="${DDFG_UPSTREAM}"
|
||||
DDFG_CHAIN_HASH="${DDFG_CHAIN_HASH}"
|
||||
WINFG_UPSTREAM="${WINFG_UPSTREAM}"
|
||||
WINFG_CHAIN_HASH="${WINFG_CHAIN_HASH}"
|
||||
)
|
||||
target_link_libraries(droiddeckwayland dxbc atomic)
|
||||
else()
|
||||
|
||||
@@ -86,7 +86,7 @@ static int64_t now_ns(void) {
|
||||
|
||||
|
||||
void vkp_framegen_set_engine(int kind) {
|
||||
if (kind != VKP_FG_ENGINE_LSFG && kind != VKP_FG_ENGINE_DDFG) kind = VKP_FG_ENGINE_LSFG;
|
||||
if (kind != VKP_FG_ENGINE_LSFG && kind != VKP_FG_ENGINE_WINFG) kind = VKP_FG_ENGINE_LSFG;
|
||||
atomic_store(&g_kind, kind);
|
||||
atomic_store(&g_failed_kind, -1);
|
||||
atomic_store(&g_cfg_dirty, 1);
|
||||
@@ -125,7 +125,7 @@ void vkp_framegen_set_tuning(float flow_scale, float refresh_hz) {
|
||||
atomic_store(&g_cfg_dirty, 1);
|
||||
}
|
||||
|
||||
void vkp_framegen_set_ddfg_tuning(int model, int perf_preset) {
|
||||
void vkp_framegen_set_winfg_tuning(int model, int perf_preset) {
|
||||
atomic_store(&g_model, model == 3 ? 3 : 4);
|
||||
atomic_store(&g_preset, perf_preset < 0 ? 0 : perf_preset > 2 ? 2 : perf_preset);
|
||||
atomic_store(&g_cfg_dirty, 1);
|
||||
@@ -180,9 +180,9 @@ void vkp_framegen_device_ready(VkDevice dev, VkQueue queue, uint32_t qfam, int f
|
||||
g_vk.GetPhysicalDeviceMemoryProperties(g_pd, &g_memprops);
|
||||
fge_device_ready(g_gipa, g_inst, g_pd, dev, queue, qfam, features_enabled);
|
||||
g_dev_ready = vk.CreateImage && vk.CreateImageView && vk.CmdPipelineBarrier;
|
||||
FGLOG("engines ready on the compositor's device: LSFG Native %s (%s), DD-FG Native %s",
|
||||
FGLOG("engines ready on the compositor's device: LSFG Native %s (%s), Win-FG Native %s",
|
||||
fge_caps_ok(VKP_FG_ENGINE_LSFG) ? "available" : "unavailable", fge_caps_reason(),
|
||||
fge_caps_ok(VKP_FG_ENGINE_DDFG) ? "available" : "unavailable");
|
||||
fge_caps_ok(VKP_FG_ENGINE_WINFG) ? "available" : "unavailable");
|
||||
}
|
||||
|
||||
void vkp_framegen_device_lost(void) { g_dev_lost = 1; }
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
#endif
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
enum { VKP_FG_ENGINE_LSFG = 0, VKP_FG_ENGINE_DDFG = 1 };
|
||||
enum { VKP_FG_ENGINE_LSFG = 0, VKP_FG_ENGINE_WINFG = 1 };
|
||||
#define VKP_FG_MAX_GENERATIONS 3
|
||||
|
||||
void vkp_framegen_set_engine(int kind);
|
||||
void vkp_framegen_set_armed(int armed, int multiplier, int target_fps);
|
||||
void vkp_framegen_set_lsfg_cache_path(const char *path);
|
||||
void vkp_framegen_set_tuning(float flow_scale, float refresh_hz);
|
||||
void vkp_framegen_set_ddfg_tuning(int model, int perf_preset);
|
||||
void vkp_framegen_set_winfg_tuning(int model, int perf_preset);
|
||||
int vkp_framegen_problem(void);
|
||||
const char *vkp_framegen_caps_reason(void);
|
||||
void vkp_framegen_stats(float out[6]);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "lsfg/lsfg_engine.h"
|
||||
#include "lsfg/lsfg_probe.h"
|
||||
#include "lsfg/lsfg_vkd.h"
|
||||
#include "ddfg/ddfg_engine.h"
|
||||
#include "winfg/winfg_engine.h"
|
||||
|
||||
#define ELOG(...) __android_log_print(ANDROID_LOG_INFO, "DroidDeckWayland", "framegen: " __VA_ARGS__)
|
||||
#define ELOGE(...) __android_log_print(ANDROID_LOG_ERROR, "DroidDeckWayland", "framegen: " __VA_ARGS__)
|
||||
@@ -30,7 +30,7 @@ uint32_t g_qfam = 0;
|
||||
bool g_probed = false, g_ready = false;
|
||||
|
||||
std::unique_ptr<lsfg::Engine> g_lsfg;
|
||||
std::unique_ptr<ddfg::Engine> g_ddfg;
|
||||
std::unique_ptr<winfg::Engine> g_winfg;
|
||||
int g_kind = -1;
|
||||
char g_build[96] = "";
|
||||
|
||||
@@ -143,15 +143,15 @@ int fge_start(int kind, const char *cache_path) {
|
||||
fge_stop();
|
||||
if (!g_ready) return -1;
|
||||
if (kind == 1) {
|
||||
auto e = std::make_unique<ddfg::Engine>();
|
||||
auto e = std::make_unique<winfg::Engine>();
|
||||
if (!e->init(g_table, g_pd, g_dev, g_qfam, g_queue)) {
|
||||
ELOGE("ddfg-native: engine init failed");
|
||||
ELOGE("winfg-native: engine init failed");
|
||||
return -1;
|
||||
}
|
||||
g_ddfg = std::move(e);
|
||||
g_winfg = std::move(e);
|
||||
g_kind = 1;
|
||||
#if defined(DDFG_UPSTREAM) && defined(DDFG_CHAIN_HASH)
|
||||
std::snprintf(g_build, sizeof(g_build), "chain %s, src %s", DDFG_UPSTREAM, DDFG_CHAIN_HASH);
|
||||
#if defined(WINFG_UPSTREAM) && defined(WINFG_CHAIN_HASH)
|
||||
std::snprintf(g_build, sizeof(g_build), "chain %s, src %s", WINFG_UPSTREAM, WINFG_CHAIN_HASH);
|
||||
#else
|
||||
std::snprintf(g_build, sizeof(g_build), "embedded chain");
|
||||
#endif
|
||||
@@ -176,46 +176,46 @@ int fge_start(int kind, const char *cache_path) {
|
||||
}
|
||||
|
||||
void fge_stop(void) {
|
||||
if ((g_lsfg || g_ddfg) && g_table.DeviceWaitIdle && g_dev) g_table.DeviceWaitIdle(g_dev);
|
||||
if ((g_lsfg || g_winfg) && g_table.DeviceWaitIdle && g_dev) g_table.DeviceWaitIdle(g_dev);
|
||||
g_lsfg.reset();
|
||||
g_ddfg.reset();
|
||||
g_winfg.reset();
|
||||
g_kind = -1;
|
||||
}
|
||||
|
||||
int fge_unavailable(void) {
|
||||
if (g_kind == 1) return g_ddfg && g_ddfg->unavailable() ? 1 : 0;
|
||||
if (g_kind == 1) return g_winfg && g_winfg->unavailable() ? 1 : 0;
|
||||
return g_lsfg && g_lsfg->unavailable() ? 1 : 0;
|
||||
}
|
||||
|
||||
void fge_configure(uint32_t multiplier, uint32_t target_fps, float flow_scale, float refresh_hz,
|
||||
int model, int perf_preset) {
|
||||
if (multiplier < 2) multiplier = 2;
|
||||
if (g_kind == 1 && g_ddfg) g_ddfg->configure(multiplier, model, perf_preset, flow_scale);
|
||||
if (g_kind == 1 && g_winfg) g_winfg->configure(multiplier, model, perf_preset, flow_scale);
|
||||
else if (g_lsfg) g_lsfg->configure(multiplier, target_fps, flow_scale, refresh_hz);
|
||||
}
|
||||
|
||||
int fge_prepare(uint32_t w, uint32_t h, VkFormat fmt) {
|
||||
if (g_kind == 1) return g_ddfg && g_ddfg->prepare(w, h, fmt) ? 1 : 0;
|
||||
if (g_kind == 1) return g_winfg && g_winfg->prepare(w, h, fmt) ? 1 : 0;
|
||||
if (!g_lsfg) return 0;
|
||||
g_lsfg->setGuestExtent(w, h);
|
||||
return g_lsfg->prepare(w, h, fmt) ? 1 : 0;
|
||||
}
|
||||
|
||||
uint32_t fge_plan(uint32_t capacity, uint64_t source_frames, float presented_rate) {
|
||||
if (g_kind == 1) return g_ddfg ? g_ddfg->plan(capacity) : 0;
|
||||
if (g_kind == 1) return g_winfg ? g_winfg->plan(capacity) : 0;
|
||||
if (!g_lsfg) return 0;
|
||||
g_lsfg->setPresentedRate(presented_rate);
|
||||
return g_lsfg->plan(capacity, source_frames);
|
||||
}
|
||||
|
||||
void fge_process(VkCommandBuffer cmd, VkImage source, uint32_t w, uint32_t h, uint32_t gens) {
|
||||
if (g_kind == 1) { if (g_ddfg) g_ddfg->process(cmd, source, w, h, gens); return; }
|
||||
if (g_kind == 1) { if (g_winfg) g_winfg->process(cmd, source, w, h, gens); return; }
|
||||
if (g_lsfg) g_lsfg->process(cmd, source, w, h, gens);
|
||||
}
|
||||
|
||||
void fge_generate(VkCommandBuffer cmd, uint32_t g, uint32_t count, VkImage img, VkImageView view,
|
||||
uint32_t w, uint32_t h) {
|
||||
if (g_kind == 1) { if (g_ddfg) g_ddfg->generateInto(cmd, g, count, img, view, w, h); return; }
|
||||
if (g_kind == 1) { if (g_winfg) g_winfg->generateInto(cmd, g, count, img, view, w, h); return; }
|
||||
if (g_lsfg) g_lsfg->generateInto(cmd, g, g, img, view, w, h);
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ void fge_telemetry(float *accepted, float *source_rate, float *thermal) {
|
||||
}
|
||||
}
|
||||
|
||||
const char *fge_engine_name(int kind) { return kind == 1 ? "DD-FG Native" : "LSFG Native"; }
|
||||
const char *fge_engine_name(int kind) { return kind == 1 ? "Win-FG Native" : "LSFG Native"; }
|
||||
const char *fge_build_info(void) { return g_build; }
|
||||
|
||||
}
|
||||
@@ -34,5 +34,5 @@ void fge_forget_targets(void) {}
|
||||
void fge_telemetry(float *accepted, float *source_rate, float *thermal) {
|
||||
*accepted = 0.0f; *source_rate = 0.0f; *thermal = -1.0f;
|
||||
}
|
||||
const char *fge_engine_name(int kind) { return kind == 1 ? "DD-FG Native" : "LSFG Native"; }
|
||||
const char *fge_engine_name(int kind) { return kind == 1 ? "Win-FG Native" : "LSFG Native"; }
|
||||
const char *fge_build_info(void) { return "stub"; }
|
||||
@@ -31,10 +31,10 @@ Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeSetFrameGenTuning(JN
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeSetDdFgTuning(JNIEnv *env, jclass clazz,
|
||||
Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeSetWinFgTuning(JNIEnv *env, jclass clazz,
|
||||
jint model, jint perfPreset) {
|
||||
(void)env; (void)clazz;
|
||||
vkp_framegen_set_ddfg_tuning((int)model, (int)perfPreset);
|
||||
vkp_framegen_set_winfg_tuning((int)model, (int)perfPreset);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
|
||||
@@ -35,6 +35,5 @@ class App : Application() {
|
||||
com.droiddeck.launcher.stores.StoresState.register(com.droiddeck.launcher.stores.gog.GogBackend)
|
||||
com.droiddeck.launcher.stores.StoresState.register(com.droiddeck.launcher.stores.epic.EpicBackend)
|
||||
com.droiddeck.launcher.stores.StoresState.register(com.droiddeck.launcher.stores.amazon.AmazonBackend)
|
||||
com.droiddeck.launcher.stores.StoresState.register(com.droiddeck.launcher.stores.itch.ItchBackend)
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.droiddeck.launcher
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
|
||||
object LauncherIcon {
|
||||
const val DARK = "dark"
|
||||
const val LIGHT = "light"
|
||||
|
||||
private val aliases = linkedMapOf(DARK to "com.droiddeck.launcher.LauncherDark", LIGHT to "com.droiddeck.launcher.LauncherLight")
|
||||
|
||||
val options: List<Pair<String, Int>> = listOf(
|
||||
DARK to R.string.setup_launcher_icon_dark,
|
||||
LIGHT to R.string.setup_launcher_icon_light,
|
||||
)
|
||||
|
||||
private fun component(context: Context, style: String) =
|
||||
ComponentName(context, aliases.getValue(style))
|
||||
|
||||
private fun enabled(context: Context, style: String): Boolean =
|
||||
when (context.packageManager.getComponentEnabledSetting(component(context, style))) {
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> style == DARK
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun current(context: Context): String = aliases.keys.firstOrNull { enabled(context, it) } ?: DARK
|
||||
|
||||
fun activity(context: Context): ComponentName = component(context, current(context))
|
||||
|
||||
fun set(context: Context, style: String) {
|
||||
if (style !in aliases || style == current(context)) return
|
||||
val pm = context.packageManager
|
||||
pm.setComponentEnabledSetting(component(context, style),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP)
|
||||
aliases.keys.filter { it != style }.forEach {
|
||||
pm.setComponentEnabledSetting(component(context, it),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,7 +164,6 @@ class MainActivity : ComponentActivity() {
|
||||
private var downloadMirror by mutableStateOf("")
|
||||
private var downloadPrefix by mutableStateOf("")
|
||||
private var animationsEnabled by mutableStateOf(true)
|
||||
private var launcherIcon by mutableStateOf(LauncherIcon.DARK)
|
||||
private var gameStoresEnabled by mutableStateOf(true)
|
||||
private var gameStoresSpeedTier by mutableStateOf("fast")
|
||||
private var storesOpenTab by mutableStateOf(SessionPrefs.STORES_OPEN_LIBRARY)
|
||||
@@ -485,20 +484,6 @@ class MainActivity : ComponentActivity() {
|
||||
pickSaveDir.launch(InAppFilePicker.buildDirIntent(this, getString(R.string.game_file_folder), gameSyncFolder))
|
||||
}
|
||||
|
||||
private fun switchLauncherIcon(style: String): Boolean {
|
||||
val before = LauncherIcon.current(this)
|
||||
LauncherIcon.set(this, style)
|
||||
launcherIcon = LauncherIcon.current(this)
|
||||
if (launcherIcon == before) return false
|
||||
// Android finishes activities started through the alias that was just disabled.
|
||||
startActivity(Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
.setComponent(LauncherIcon.activity(this))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||
.putExtra(com.droiddeck.launcher.stores.download.StoreDownloadService.EXTRA_NAV, "setup:launcher"))
|
||||
finishAndRemoveTask()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun readGameIntent(request: Intent) {
|
||||
request.getStringExtra(com.droiddeck.launcher.stores.download.StoreDownloadService.EXTRA_NAV)?.let { navRequest = it; request.removeExtra(com.droiddeck.launcher.stores.download.StoreDownloadService.EXTRA_NAV) }
|
||||
shortcutPicker = request.action == Intent.ACTION_CREATE_SHORTCUT
|
||||
@@ -754,7 +739,6 @@ class MainActivity : ComponentActivity() {
|
||||
downloadPrefix = DownloadSource.custom(this)
|
||||
DownloadSource.probe()
|
||||
animationsEnabled = SessionPrefs.animationsEnabled(this)
|
||||
launcherIcon = LauncherIcon.current(this)
|
||||
gameStoresEnabled = SessionPrefs.gameStoresEnabled(this)
|
||||
gameStoresSpeedTier = SessionPrefs.gameStoresSpeedTier(this)
|
||||
storesOpenTab = SessionPrefs.storesOpenTab(this)
|
||||
@@ -815,7 +799,6 @@ class MainActivity : ComponentActivity() {
|
||||
downloadMirror = downloadMirror,
|
||||
downloadPrefix = downloadPrefix,
|
||||
animationsEnabled = animationsEnabled,
|
||||
launcherIcon = launcherIcon,
|
||||
gameStoresEnabled = gameStoresEnabled,
|
||||
gameStoresSpeedTier = gameStoresSpeedTier,
|
||||
storesOpenTab = storesOpenTab,
|
||||
@@ -969,7 +952,6 @@ class MainActivity : ComponentActivity() {
|
||||
onPageBack = { showPerformance = false; showComponents = false; showMapping = false },
|
||||
onGameStoresEnabled = { on -> SessionPrefs.setGameStoresEnabled(this, on); gameStoresEnabled = on },
|
||||
onTheme = { id -> SessionPrefs.setTheme(this, id); theme = id },
|
||||
onLauncherIcon = { style -> switchLauncherIcon(style) },
|
||||
onLanguage = { tag ->
|
||||
if (tag != com.droiddeck.launcher.core.AppLanguage.chosen(this)) {
|
||||
com.droiddeck.launcher.core.AppLanguage.setChosen(this, tag)
|
||||
@@ -1900,7 +1882,7 @@ class MainActivity : ComponentActivity() {
|
||||
val listX64 = SessionPrefs.activeSteamClientArch(this) == "x86_64"
|
||||
if (listX64 != steamGamesX64) {
|
||||
steamGamesX64 = listX64
|
||||
steamGames = steamGames.filterNot { Library.fromSteamClient(it) || (it.library == Library.ADDED && it.gameId in 1..0xFFFFFFFFL) }
|
||||
steamGames = steamGames.filterNot(Library::fromSteamClient)
|
||||
}
|
||||
if (!busy && LinuxRuntimeInstaller.isBusy()) followRuntimeOperation { LinuxRuntimeInstaller.attach(it) }
|
||||
if (showComponents) drivers.refreshDrivers()
|
||||
@@ -1934,13 +1916,12 @@ class MainActivity : ComponentActivity() {
|
||||
ui.post { if (scanGeneration == libraryScanGeneration) emulatorList = emus }
|
||||
if (ready) {
|
||||
val quick = runCatching { Library.steamGames(this) }.getOrNull()
|
||||
val claimed = quick?.let { runCatching { Library.claimedFolders(it) }.getOrDefault(emptySet()) }.orEmpty()
|
||||
if (quick != null) ui.post {
|
||||
if (scanGeneration == libraryScanGeneration && steamGames.isNotEmpty()) {
|
||||
val before = steamGames.map { it.gameId }.toSet()
|
||||
val fresh = quick.filter { it.gameId !in before }
|
||||
if (fresh.isNotEmpty()) {
|
||||
steamGames = (quick + steamGames.filter { it.library == Library.ADDED && it.gameFiles?.absolutePath.let { path -> path == null || path !in claimed } }).distinctBy { it.gameId }
|
||||
steamGames = (quick + steamGames.filter { it.library == Library.ADDED }).distinctBy { it.gameId }
|
||||
fresh.forEach { com.droiddeck.launcher.frontend.AutoComponents.queueGame(this, it, asked = false) }
|
||||
}
|
||||
}
|
||||
@@ -2063,7 +2044,7 @@ class MainActivity : ComponentActivity() {
|
||||
com.droiddeck.launcher.core.AppLanguage.setChosen(this, com.droiddeck.launcher.core.AppLanguage.SYSTEM)
|
||||
if (!ok) android.widget.Toast.makeText(this, R.string.setup_reset_settings_partial, android.widget.Toast.LENGTH_LONG).show()
|
||||
com.droiddeck.launcher.ui.Motion.refresh(this)
|
||||
if (!switchLauncherIcon(LauncherIcon.DARK)) recreate()
|
||||
recreate()
|
||||
}
|
||||
}, "reset-settings").start()
|
||||
}
|
||||
|
||||
@@ -1609,9 +1609,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
glassesScreen?.hz?.takeIf { it > 1f }?.let { return it }
|
||||
val display = if (Build.VERSION.SDK_INT >= 30) display else windowManager.defaultDisplay
|
||||
val wanted = window.attributes.preferredDisplayModeId
|
||||
val requested = display?.supportedModes?.firstOrNull { wanted != 0 && it.modeId == wanted }?.refreshRate
|
||||
val running = display?.refreshRate?.takeIf { it > 1f }
|
||||
val hz = listOfNotNull(requested, running).minOrNull() ?: 60f
|
||||
val hz = display?.supportedModes?.firstOrNull { wanted != 0 && it.modeId == wanted }?.refreshRate
|
||||
?: display?.refreshRate ?: 60f
|
||||
val mirrorCap = mirrorRefreshLimit().takeIf { it > 0 }?.toFloat() ?: Float.POSITIVE_INFINITY
|
||||
val capped = minOf(hz, PerfMode.peakRefreshCap(this), mirrorCap)
|
||||
return if (capped > 1f) capped else 60f
|
||||
|
||||
@@ -200,7 +200,6 @@ object DeviceReport {
|
||||
h("Drivers")
|
||||
val gpu = com.droiddeck.launcher.gpu.GpuInfo.detect()
|
||||
k("GPU", "${gpu.name} · ${gpu.family.label} · ${gpu.supportText}" + if (gpu.oneUi8Gen2) " · One UI 8 Gen 2" else "")
|
||||
if (gpu.model == 740) k("TP UBWC hint", com.droiddeck.launcher.gpu.TpUbwcHint.forThisDevice() ?: "driver default")
|
||||
k("Driver mode", SessionPrefs.gpuDriverMode(context) +
|
||||
(com.droiddeck.launcher.gpu.DriverPairs.recommendedKey(gpu, com.droiddeck.launcher.gpu.DriverPairs.from(
|
||||
com.droiddeck.launcher.gpu.TurnipReleases.cached(context)), com.droiddeck.launcher.gpu.TurnipReleases.autoPicks(context),
|
||||
|
||||
@@ -32,6 +32,7 @@ object GameEnvironmentOptions {
|
||||
Option("MESA_EXTENSION_MAX_YEAR", "", 0, Type.TEXT),
|
||||
Option("MESA_GL_VERSION_OVERRIDE", "", 0, Type.TEXT),
|
||||
Option("PULSE_LATENCY_MSEC", "", 0, Type.NUMBER),
|
||||
Option("DROIDDECK_ANTICHEAT_OFFLINE", "1", R.string.game_env_anticheat_offline, Type.TOGGLE, listOf("0", "1")),
|
||||
Option("WINE_LARGE_ADDRESS_AWARE", "0", 0, Type.TOGGLE, listOf("0", "1")),
|
||||
Option("WINEDLLOVERRIDES", "", 0, Type.TEXT),
|
||||
Option("GALLIUM_HUD", "simple", 0, Type.MULTIPLE, listOf("simple", "fps", "frametime")),
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.Icon
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import com.droiddeck.launcher.LauncherIcon
|
||||
import com.droiddeck.launcher.MainActivity
|
||||
import com.droiddeck.launcher.R
|
||||
import java.io.File
|
||||
@@ -24,7 +23,7 @@ object GameShortcuts {
|
||||
val name = game.name.ifBlank { context.getString(R.string.app_name) }
|
||||
val icon = iconBitmap(game)?.let(Icon::createWithBitmap) ?: Icon.createWithResource(context, R.mipmap.ic_launcher)
|
||||
return ShortcutInfo.Builder(context, "game:${game.gameIdString}")
|
||||
.setActivity(LauncherIcon.activity(context))
|
||||
.setActivity(android.content.ComponentName(context, MainActivity::class.java))
|
||||
.setShortLabel(name.take(40)).setLongLabel(name).setIcon(icon)
|
||||
.setIntent(launchIntent(context, game.gameIdString)).build()
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ object Library {
|
||||
private val NAME = Regex("^\\s*\"name\"\\s*\"([^\"]*)\"", RegexOption.MULTILINE)
|
||||
private val STATE = Regex("^\\s*\"StateFlags\"\\s*\"(\\d+)\"", RegexOption.MULTILINE)
|
||||
private val LAST_PLAYED = Regex("^\\s*\"LastPlayed\"\\s*\"(\\d+)\"", RegexOption.MULTILINE)
|
||||
private val PRIVATE_APPS = Regex("\"PrivateApps_\\d+\"\\s*\"\\[([^\\]]*)\\]\"")
|
||||
private val INSTALL_DIR = Regex("^\\s*\"installdir\"\\s*\"([^\"]*)\"", RegexOption.MULTILINE)
|
||||
|
||||
internal fun steamLibraries(context: Context, arch: String = SessionPrefs.activeSteamClientArch(context)): List<Pair<File, String>> {
|
||||
@@ -138,35 +137,13 @@ object Library {
|
||||
}
|
||||
|
||||
fun steamLibraryArt(context: Context): List<Pair<Int, File>> {
|
||||
val root = SessionPrefs.activeSteamRoot(context)
|
||||
val cache = File(root, "appcache/librarycache")
|
||||
val concealed = concealedSteamApps(root)
|
||||
val cache = File(SessionPrefs.activeSteamRoot(context), "appcache/librarycache")
|
||||
return cache.listFiles().orEmpty().mapNotNull { dir ->
|
||||
val appId = dir.name.toIntOrNull()?.takeIf { it !in NOT_GAMES && it.toLong() !in concealed } ?: return@mapNotNull null
|
||||
val appId = dir.name.toIntOrNull()?.takeIf { it !in NOT_GAMES } ?: return@mapNotNull null
|
||||
steamCacheImage(cache, appId, STEAM_CAPSULES)?.let { appId to it }
|
||||
}
|
||||
}
|
||||
|
||||
/** Apps the user hid (the "hidden" collection) or marked private in Steam. */
|
||||
internal fun concealedSteamApps(root: File): Set<Long> {
|
||||
val config = AddedGames.accountConfig(root) ?: return emptySet()
|
||||
val hidden = runCatching {
|
||||
val entries = org.json.JSONArray(File(config, "cloudstorage/cloud-storage-namespace-1.json").readText())
|
||||
val entry = (0 until entries.length()).map { entries.getJSONArray(it) }
|
||||
.firstOrNull { it.optString(0) == "user-collections.hidden" }?.optJSONObject(1)
|
||||
if (entry == null || entry.optBoolean("is_deleted")) emptySet() else {
|
||||
val collection = org.json.JSONObject(entry.optString("value"))
|
||||
fun ids(key: String) = collection.optJSONArray(key)?.let { a -> (0 until a.length()).map { a.optLong(it) } }.orEmpty()
|
||||
ids("added").toSet() - ids("removed").toSet()
|
||||
}
|
||||
}.getOrDefault(emptySet())
|
||||
val private = runCatching {
|
||||
PRIVATE_APPS.find(File(config, "localconfig.vdf").readText())?.groupValues?.get(1)
|
||||
?.split(',')?.mapNotNull { it.trim().toLongOrNull() }?.toSet()
|
||||
}.getOrNull().orEmpty()
|
||||
return hidden + private
|
||||
}
|
||||
|
||||
fun fromSteamClient(g: SteamGame): Boolean = g.library != ADDED && g.source == SOURCE_STEAM
|
||||
|
||||
fun launchableGames(
|
||||
@@ -178,23 +155,9 @@ object Library {
|
||||
var steam = steamGames(context, strictRead, arch)
|
||||
val targets = steam.filter(::fromSteamClient).map { SteamGrid.target(it, context) } + list.map { SteamGrid.target(it, context) }
|
||||
if (runCatching { SteamGrid.sync(context, targets) }.getOrDefault(false)) steam = steamGames(context, strictRead, arch)
|
||||
val claimed = claimedFolders(steam)
|
||||
val own = list.filter { g ->
|
||||
g.steamAppId != null || claimed.isEmpty() ||
|
||||
(runCatching { g.folder.canonicalPath }.getOrDefault(g.folder.absolutePath) !in claimed &&
|
||||
AddedGames.guestPath(context, g.folder).let { it == null || it !in claimed })
|
||||
}
|
||||
return (steam + own.map { g -> addedGame(context, g, roots) }).distinctBy { it.gameId }
|
||||
return (steam + list.map { g -> addedGame(context, g, roots) }).distinctBy { it.gameId }
|
||||
}
|
||||
|
||||
fun claimedFolders(steam: List<SteamGame>): Set<String> = steam.filter(::fromSteamClient).flatMap { g ->
|
||||
val dir = g.gameFiles ?: return@flatMap emptyList()
|
||||
listOfNotNull(
|
||||
runCatching { dir.canonicalPath }.getOrNull(),
|
||||
runCatching { java.nio.file.Files.readSymbolicLink(dir.toPath()).toString() }.getOrNull(),
|
||||
)
|
||||
}.toSet()
|
||||
|
||||
fun addedGame(context: Context, g: AddedGames.Game, roots: List<File> = steamLibraries(context).map { it.first }): SteamGame =
|
||||
AddedGameArt.resolve(context, g).let { art ->
|
||||
SteamGame(
|
||||
|
||||
@@ -7,13 +7,12 @@ import com.droiddeck.launcher.wayland.WaylandCompositor
|
||||
|
||||
object FrameGen {
|
||||
const val ENGINE_OFF = "off"
|
||||
const val ENGINE_DDFG = "ddfg"
|
||||
private const val LEGACY_ENGINE_WINFG = "winfg"
|
||||
const val ENGINE_WINFG = "winfg"
|
||||
const val ENGINE_LSFG = "lsfg"
|
||||
|
||||
private const val DDFG_PERF_PRESET = 2
|
||||
private const val DDFG_MODEL = 4
|
||||
private const val FLOW_SCALE_DDFG = 0.60f
|
||||
private const val WINFG_PERF_PRESET = 2
|
||||
private const val WINFG_MODEL = 4
|
||||
private const val FLOW_SCALE_WINFG = 0.60f
|
||||
private const val FLOW_SCALE_LSFG = 0.80f
|
||||
|
||||
val ADAPTIVE_TARGETS = listOf(120, 90, 60)
|
||||
@@ -30,7 +29,7 @@ object FrameGen {
|
||||
|
||||
fun mode(context: Context): Mode {
|
||||
val prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
val engine = prefs.getString("engine", ENGINE_OFF).let { if (it == LEGACY_ENGINE_WINFG) ENGINE_DDFG else it ?: ENGINE_OFF }
|
||||
val engine = prefs.getString("engine", ENGINE_OFF) ?: ENGINE_OFF
|
||||
val target = prefs.getInt("target", 0)
|
||||
if (engine == ENGINE_LSFG && target in ADAPTIVE_TARGETS) return Mode(engine, target = target)
|
||||
return Mode(engine, prefs.getInt("multiplier", 2).coerceIn(2, 4))
|
||||
@@ -44,7 +43,7 @@ object FrameGen {
|
||||
fun label(context: Context): String = label(context, mode(context))
|
||||
|
||||
fun label(context: Context, mode: Mode): String = when {
|
||||
mode.engine == ENGINE_DDFG -> context.getString(R.string.frame_gen_ddfg, mode.multiplier)
|
||||
mode.engine == ENGINE_WINFG -> context.getString(R.string.frame_gen_winfg, mode.multiplier)
|
||||
mode.engine == ENGINE_LSFG && mode.target > 0 -> context.getString(R.string.frame_gen_lsfg_adaptive, mode.target)
|
||||
mode.engine == ENGINE_LSFG -> context.getString(R.string.frame_gen_lsfg, mode.multiplier)
|
||||
else -> context.getString(R.string.frame_gen_off)
|
||||
@@ -69,10 +68,10 @@ object FrameGen {
|
||||
WaylandCompositor.nativeSetFrameGenTuning(FLOW_SCALE_LSFG, refreshHz)
|
||||
WaylandCompositor.nativeSetFrameGenArmed(true, mode.multiplier, mode.target)
|
||||
}
|
||||
ENGINE_DDFG -> {
|
||||
WaylandCompositor.nativeSetFrameGenEngine(WaylandCompositor.FG_ENGINE_DDFG)
|
||||
WaylandCompositor.nativeSetDdFgTuning(DDFG_MODEL, DDFG_PERF_PRESET)
|
||||
WaylandCompositor.nativeSetFrameGenTuning(FLOW_SCALE_DDFG, refreshHz)
|
||||
ENGINE_WINFG -> {
|
||||
WaylandCompositor.nativeSetFrameGenEngine(WaylandCompositor.FG_ENGINE_WINFG)
|
||||
WaylandCompositor.nativeSetWinFgTuning(WINFG_MODEL, WINFG_PERF_PRESET)
|
||||
WaylandCompositor.nativeSetFrameGenTuning(FLOW_SCALE_WINFG, refreshHz)
|
||||
WaylandCompositor.nativeSetFrameGenArmed(true, mode.multiplier, 0)
|
||||
}
|
||||
else -> WaylandCompositor.nativeSetFrameGenArmed(false, 0, 0)
|
||||
|
||||
@@ -11,10 +11,8 @@ object TpUbwcHint {
|
||||
|
||||
// On Adreno 740, TPL1_DBG_ECO_CNTL1.TP_UBWC_FLAG_HINT is shared by every driver on the GPU and must
|
||||
// match the system driver: blob v6xx leaves it off, newer blobs set it. A mismatch corrupts Android UI.
|
||||
// Samsung's One UI firmware sets it whatever blob version it reports.
|
||||
internal fun features(model: Int, systemDriverVersion: String?, samsung: Boolean = false): String? {
|
||||
internal fun features(model: Int, systemDriverVersion: String?): String? {
|
||||
if (model != 740) return null
|
||||
if (samsung) return "enable_tp_ubwc_flag_hint=1"
|
||||
val blob = systemDriverVersion?.split('.')?.getOrNull(1)?.toIntOrNull() ?: return null
|
||||
return "enable_tp_ubwc_flag_hint=" + if (blob >= 700) "1" else "0"
|
||||
}
|
||||
@@ -22,12 +20,11 @@ object TpUbwcHint {
|
||||
@Synchronized
|
||||
fun forThisDevice(): String? {
|
||||
if (resolved) return cached
|
||||
val gpu = GpuInfo.detect()
|
||||
val model = gpu.model
|
||||
val model = GpuInfo.detect().model
|
||||
val version = if (model == 740) VulkanInfo.query()["driver_version"] else null
|
||||
cached = features(model, version, gpu.oneUi8Gen2)
|
||||
cached = features(model, version)
|
||||
resolved = true
|
||||
Log.i(TAG, "Adreno $model${if (gpu.oneUi8Gen2) " (One UI)" else ""}, system driver ${version ?: "n/a"}: ${cached ?: "driver default"}")
|
||||
Log.i(TAG, "Adreno $model, system driver ${version ?: "n/a"}: ${cached ?: "driver default"}")
|
||||
return cached
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.io.File
|
||||
|
||||
object GuestCommand {
|
||||
fun run(context: Context, argv: List<String>, fakeRoot: Boolean = false, logName: String? = null,
|
||||
linkDir: File? = null, extraBinds: List<String>? = null, fastPath: Boolean = false, onLine: (String) -> Unit): Int {
|
||||
linkDir: File? = null, onLine: (String) -> Unit): Int {
|
||||
val log = logName?.let {
|
||||
try {
|
||||
File(File(LinuxRuntime.logDir(context), SessionPaths.TOOLS_DIR).apply { mkdirs() }, "$it.log").printWriter()
|
||||
@@ -19,14 +19,13 @@ object GuestCommand {
|
||||
}
|
||||
log?.println("== ${java.util.Date()} ${argv.joinToString(" ")}")
|
||||
try {
|
||||
return runLogged(context, argv, fakeRoot, linkDir, extraBinds, fastPath) { line -> log?.println(line); log?.flush(); onLine(line) }
|
||||
return runLogged(context, argv, fakeRoot, linkDir) { line -> log?.println(line); log?.flush(); onLine(line) }
|
||||
} finally {
|
||||
log?.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun runLogged(context: Context, argv: List<String>, fakeRoot: Boolean, linkDir: File?, extraBinds: List<String>?,
|
||||
fastPath: Boolean, onLine: (String) -> Unit): Int {
|
||||
private fun runLogged(context: Context, argv: List<String>, fakeRoot: Boolean, linkDir: File?, onLine: (String) -> Unit): Int {
|
||||
val root = LinuxRuntime.rootDir(context)
|
||||
LinuxRuntime.writeAccounts(context)
|
||||
SessionFiles.stage(context, root)
|
||||
@@ -34,10 +33,8 @@ object GuestCommand {
|
||||
val runtimeDir = File(context.filesDir, ".flatpak-rt").apply { mkdirs() }
|
||||
val cmd = LinuxRuntime.prootPrefix(context, root, "/root", fakeRoot)
|
||||
if (linkDir != null) cmd.add(1, "--link2symlink")
|
||||
val binds = LinuxRuntime.binds(context, null, runtimeDir, Environment.getExternalStorageDirectory(), extraBinds)
|
||||
binds.forEach { cmd.add("-b"); cmd.add(it) }
|
||||
// Fake root and link2symlink are emulated by proot, so their syscalls must stay on the traced path.
|
||||
val fastPathKey = if (fastPath && !fakeRoot && linkDir == null && ProotFastPath.enabled(context)) ProotFastPath.key(root, binds) else null
|
||||
LinuxRuntime.binds(context, null, runtimeDir, Environment.getExternalStorageDirectory(), null)
|
||||
.forEach { cmd.add("-b"); cmd.add(it) }
|
||||
cmd += listOf(
|
||||
"/usr/bin/env", "-i", "HOME=/root", "USER=root", "LANG=C.UTF-8",
|
||||
"PATH=/usr/local/bin:/usr/bin:/bin", "XDG_RUNTIME_DIR=${LinuxRuntime.GUEST_RUNTIME_DIR}",
|
||||
@@ -45,7 +42,6 @@ object GuestCommand {
|
||||
"DROIDDECK_BWRAP_SOCKET=${BwrapSpawner.socket(context)}",
|
||||
"XDG_DATA_DIRS=/root/.local/share/flatpak/exports/share:/usr/local/share:/usr/share",
|
||||
)
|
||||
fastPathKey?.let { cmd += ProotFastPath.guestEnv(root, binds, it) }
|
||||
cmd += argv
|
||||
val builder = ProcessBuilder(cmd).directory(root).redirectErrorStream(true)
|
||||
builder.environment().apply {
|
||||
@@ -53,7 +49,6 @@ object GuestCommand {
|
||||
put("PROOT_TMP_DIR", LinuxRuntime.prootTempDir(context).path)
|
||||
if (linkDir != null) put("PROOT_L2S_DIR", linkDir.path)
|
||||
if (SessionPrefs.prootNoSeccomp(context)) put("PROOT_NO_SECCOMP", "1")
|
||||
fastPathKey?.let { ProotFastPath.hostEnv(it).let { (k, v) -> put(k, v) } }
|
||||
LinuxRuntime.prootLibraryPath(context).takeIf { it.isNotEmpty() }?.let { put("LD_LIBRARY_PATH", it) }
|
||||
}
|
||||
val process = builder.start()
|
||||
|
||||
@@ -214,9 +214,6 @@ public final class LinuxRuntime {
|
||||
bind(cmd, fake.getPath() + ":" + entry[1]);
|
||||
}
|
||||
}
|
||||
if (unreadablePciDevice()) {
|
||||
bind(cmd, new File(root, "etc/droiddeck/empty").getPath() + ":/sys/bus/pci/devices");
|
||||
}
|
||||
bindGpuNode(context, cmd);
|
||||
bindAdrenoStats(cmd, extraBinds);
|
||||
bindCpuTemps(cmd, root);
|
||||
@@ -228,15 +225,6 @@ public final class LinuxRuntime {
|
||||
return specs;
|
||||
}
|
||||
|
||||
private static boolean unreadablePciDevice() {
|
||||
File[] devices = new File("/sys/bus/pci/devices").listFiles();
|
||||
if (devices == null) return false;
|
||||
for (File device : devices) {
|
||||
if (!new File(device, "vendor").canRead()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void bindGpuNode(Context context, List<String> cmd) {
|
||||
StructStat st;
|
||||
try {
|
||||
|
||||
@@ -77,7 +77,7 @@ class PerfHud(context: Context) {
|
||||
val mode = FrameGen.mode(context)
|
||||
if (mode.engine == FrameGen.ENGINE_OFF) return fps(base)
|
||||
val lsfg = mode.engine == FrameGen.ENGINE_LSFG
|
||||
val name = if (lsfg) "LSFG" else "DD-FG"
|
||||
val name = if (lsfg) "LSFG" else "Win-FG"
|
||||
val label = FrameGen.label(context, mode)
|
||||
if (lsfg && Lossless.cacheFile(context)?.isFile != true) return context.getString(R.string.hud_fg_needs_lossless, name)
|
||||
val stats = try { WaylandCompositor.nativeFrameGenStats() } catch (t: Throwable) { null }
|
||||
|
||||
@@ -119,7 +119,7 @@ object SessionPrefs {
|
||||
}
|
||||
|
||||
val STORES_SORTS = listOf("lastPlayed", "recentlyInstalled", "titleAsc", "titleDesc", "sizeDesc", "sizeAsc", "releaseDateDesc")
|
||||
val STORES_VIEWS = listOf("cards", "capsules", "list")
|
||||
val STORES_VIEWS = listOf("cards", "list")
|
||||
|
||||
const val STORES_OPEN_LIBRARY = "library"
|
||||
const val STORES_OPEN_STORE = "store"
|
||||
|
||||
@@ -57,7 +57,7 @@ object CloudSaves {
|
||||
val item = sidecar.extra["catalogItemId"].orEmpty()
|
||||
if (ns.isEmpty() || item.isEmpty()) "" else EpicApiClient.getCustomAttribute(token, ns, item, "CloudSaveFolder")
|
||||
}
|
||||
Store.AMAZON, Store.ITCH -> ""
|
||||
Store.AMAZON -> ""
|
||||
} ?: return null
|
||||
p.edit().putString("cloud_template_$id", fetched).apply()
|
||||
Log.i(TAG, "cloud ${store.id} $id template ${if (fetched.isEmpty()) "none" else "found"}")
|
||||
@@ -95,7 +95,7 @@ object CloudSaves {
|
||||
val dir = when (store) {
|
||||
Store.GOG -> CloudSavePaths.gog(template, pfx, folder)
|
||||
Store.EPIC -> CloudSavePaths.epic(template, pfx, folder, EpicCredentialStore.load(context)?.accountId.orEmpty(), id)
|
||||
Store.AMAZON, Store.ITCH -> null
|
||||
Store.AMAZON -> null
|
||||
}
|
||||
return dir to (if (dir == null) "unresolved" else "ok")
|
||||
}
|
||||
@@ -120,7 +120,7 @@ object CloudSaves {
|
||||
val account = EpicCredentialStore.load(context)?.accountId.orEmpty()
|
||||
if (token == null || account.isEmpty()) null else EpicCloud(account, id, token)
|
||||
}
|
||||
Store.AMAZON, Store.ITCH -> null
|
||||
Store.AMAZON -> null
|
||||
}
|
||||
|
||||
private fun gogScopedToken(context: Context, id: String, clientId: String): String? {
|
||||
|
||||
@@ -12,7 +12,6 @@ internal object LoginFlows {
|
||||
Store.GOG -> GogFlow(state)
|
||||
Store.EPIC -> epicFlow()
|
||||
Store.AMAZON -> amazonFlow()
|
||||
Store.ITCH -> null
|
||||
}
|
||||
|
||||
@Volatile var epicFlow: () -> StoreLoginActivity.Flow? = { null }
|
||||
|
||||
@@ -3,8 +3,7 @@ package com.droiddeck.launcher.stores
|
||||
enum class Store(val id: String, val label: String, val shortLabel: String) {
|
||||
GOG("gog", "GOG", "GOG"),
|
||||
EPIC("epic", "Epic Games", "Epic"),
|
||||
AMAZON("amazon", "Amazon Games", "Amazon"),
|
||||
ITCH("itch", "itch.io", "itch");
|
||||
AMAZON("amazon", "Amazon Games", "Amazon");
|
||||
|
||||
companion object {
|
||||
fun byId(id: String?): Store? = entries.firstOrNull { it.id == id }
|
||||
|
||||
@@ -54,7 +54,6 @@ object StoreSizes {
|
||||
when (item.store) {
|
||||
Store.EPIC -> EpicPrefs.get(app).edit().putLong("size_${item.id}", it).apply()
|
||||
Store.AMAZON -> AmazonPrefs.get(app).edit().putLong("size_${item.id}", it).apply()
|
||||
Store.ITCH -> {}
|
||||
Store.GOG -> {}
|
||||
}
|
||||
}
|
||||
@@ -109,6 +108,5 @@ object StoreSizes {
|
||||
AmazonManifest.parse(bytes).totalInstallSize
|
||||
}
|
||||
Store.GOG -> 0L
|
||||
Store.ITCH -> com.droiddeck.launcher.stores.itch.ItchLibrary.size(context, item)
|
||||
} }
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import java.util.Locale
|
||||
|
||||
object StoresState {
|
||||
private const val TAG = "Stores"
|
||||
private const val SEARCH_DELAY_MS = 400L
|
||||
private val main = Handler(Looper.getMainLooper())
|
||||
|
||||
val accounts = mutableStateMapOf<Store, String>()
|
||||
@@ -25,8 +24,6 @@ object StoresState {
|
||||
|
||||
val shelves = mutableStateMapOf<Store, StoreShelves>()
|
||||
|
||||
val found = mutableStateMapOf<Store, List<CatalogItem>>()
|
||||
|
||||
val status = mutableStateMapOf<Store, String>()
|
||||
|
||||
val problems = mutableStateMapOf<Store, String>()
|
||||
@@ -67,8 +64,6 @@ object StoresState {
|
||||
|
||||
private val backends = HashMap<Store, StoreBackend>()
|
||||
|
||||
private val searchSeq = java.util.concurrent.ConcurrentHashMap<Store, Int>()
|
||||
|
||||
fun backend(store: Store): StoreBackend? = backends[store]
|
||||
|
||||
fun register(backend: StoreBackend) { backends[backend.store] = backend }
|
||||
@@ -134,26 +129,6 @@ object StoresState {
|
||||
if (!shelves.containsKey(store)) backends[store]?.loadShelves(context.applicationContext, false)
|
||||
}
|
||||
|
||||
fun search(context: Context, store: Store, query: String) {
|
||||
val backend = backends[store] ?: return
|
||||
val text = query.trim()
|
||||
if (text.isEmpty()) {
|
||||
searchSeq.remove(store)
|
||||
found.remove(store)
|
||||
return
|
||||
}
|
||||
val seq = (searchSeq[store] ?: 0) + 1
|
||||
searchSeq[store] = seq
|
||||
val app = context.applicationContext
|
||||
Thread({
|
||||
Thread.sleep(SEARCH_DELAY_MS)
|
||||
if (searchSeq[store] != seq) return@Thread
|
||||
val items = runCatching { backend.search(app, text) }.getOrDefault(emptyList())
|
||||
if (searchSeq[store] != seq) return@Thread
|
||||
main.post { found[store] = items }
|
||||
}, "stores-search").start()
|
||||
}
|
||||
|
||||
fun signIn(context: Context, store: Store) {
|
||||
expired.remove(store)
|
||||
backends[store]?.signIn(context) ?: logLine("${store.label}: this build has no sign-in for it yet")
|
||||
@@ -290,7 +265,6 @@ interface StoreBackend {
|
||||
fun signOut(context: Context)
|
||||
fun syncLibrary(context: Context, force: Boolean)
|
||||
fun loadShelves(context: Context, force: Boolean)
|
||||
fun search(context: Context, query: String): List<CatalogItem> = emptyList()
|
||||
fun install(context: Context, item: CatalogItem, root: java.io.File)
|
||||
fun uninstall(context: Context, game: InstalledStoreGame)
|
||||
}
|
||||
@@ -335,7 +335,7 @@ object DownloadQueue {
|
||||
StoresState.logLine("installed \"${item.entry.name}\" → ${result}")
|
||||
StoresState.notifyLibraryChanged(context)
|
||||
val games = runCatching {
|
||||
com.droiddeck.launcher.frontend.LibraryCache.load(context).firstOrNull { it.gameFiles?.absolutePath == result }?.let { "app:${it.appId}" }
|
||||
com.droiddeck.launcher.frontend.Library.launchableGames(context).firstOrNull { it.gameFiles?.absolutePath == result }?.let { "app:${it.appId}" }
|
||||
}.getOrNull()
|
||||
StoreDownloadService.ended(context, item.entry, installed = true, gamesKey = games)
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.droiddeck.launcher.stores.itch
|
||||
|
||||
import com.droiddeck.launcher.stores.StoreNet
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
internal object ItchApi {
|
||||
const val KEYS_PAGE = "https://itch.io/user/settings/api-keys"
|
||||
|
||||
private const val BASE = "https://api.itch.io"
|
||||
private const val WEB = "https://itch.io"
|
||||
|
||||
fun get(path: String, key: String): JSONObject? {
|
||||
val body = StoreNet.get(BASE + path, bearer = key, timeoutMs = 25_000) ?: return null
|
||||
return runCatching { JSONObject(body) }.getOrNull()
|
||||
}
|
||||
|
||||
fun profile(key: String): JSONObject? = get("/profile", key)?.optJSONObject("user")
|
||||
|
||||
fun ownedKeys(key: String, page: Int): JSONObject? = get("/profile/owned-keys?page=$page&per_page=500", key)
|
||||
|
||||
fun uploads(key: String, gameId: String, downloadKeyId: String): JSONArray? =
|
||||
get("/games/$gameId/uploads?download_key_id=$downloadKeyId", key)?.optJSONArray("uploads")
|
||||
|
||||
fun search(key: String, query: String, page: Int): JSONArray? {
|
||||
val q = java.net.URLEncoder.encode(query, "UTF-8")
|
||||
return get("/search/games?query=$q&page=$page", key)?.optJSONArray("games")
|
||||
}
|
||||
|
||||
fun downloadUrl(uploadId: String, downloadKeyId: String): String =
|
||||
"$BASE/uploads/$uploadId/download?download_key_id=$downloadKeyId"
|
||||
|
||||
fun pageUrl(slug: String): String = "$WEB/$slug"
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.droiddeck.launcher.stores.itch
|
||||
|
||||
import android.content.Context
|
||||
import com.droiddeck.launcher.stores.Store
|
||||
import com.droiddeck.launcher.stores.StoreAccounts
|
||||
import org.json.JSONObject
|
||||
|
||||
internal object ItchAuth {
|
||||
private val TOKEN = Regex("[A-Za-z0-9]{40,64}")
|
||||
|
||||
fun key(context: Context): String = StoreAccounts.read(context, Store.ITCH)?.optString("access_token").orEmpty()
|
||||
|
||||
fun name(context: Context): String = StoreAccounts.signedInAs(context, Store.ITCH).orEmpty()
|
||||
|
||||
fun verify(key: String): JSONObject? = ItchApi.profile(key)
|
||||
|
||||
fun save(context: Context, key: String, user: JSONObject) {
|
||||
StoreAccounts.write(
|
||||
context,
|
||||
Store.ITCH,
|
||||
JSONObject()
|
||||
.put("access_token", key)
|
||||
.put("username", user.optString("username"))
|
||||
.put("display_name", user.optString("display_name").ifBlank { user.optString("displayName") }.ifBlank { user.optString("username") })
|
||||
.put("user_id", user.optLong("id")),
|
||||
)
|
||||
}
|
||||
|
||||
fun candidates(text: String): List<String> {
|
||||
if (text.isBlank()) return emptyList()
|
||||
val out = LinkedHashSet<String>()
|
||||
text.lineSequence().map { it.trim() }.filter { TOKEN.matches(it) }.forEach { out.add(it) }
|
||||
TOKEN.findAll(text).forEach { out.add(it.value) }
|
||||
return out.take(8)
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package com.droiddeck.launcher.stores.itch
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import com.droiddeck.launcher.R
|
||||
import com.droiddeck.launcher.stores.CatalogItem
|
||||
import com.droiddeck.launcher.stores.InstalledStoreGame
|
||||
import com.droiddeck.launcher.stores.Store
|
||||
import com.droiddeck.launcher.stores.StoreBackend
|
||||
import com.droiddeck.launcher.stores.StoreInstallRoot
|
||||
import com.droiddeck.launcher.stores.StoreSizes
|
||||
import com.droiddeck.launcher.stores.StoresState
|
||||
import com.droiddeck.launcher.stores.download.DownloadEntry
|
||||
import com.droiddeck.launcher.stores.download.DownloadQueue
|
||||
import java.io.File
|
||||
|
||||
object ItchBackend : StoreBackend {
|
||||
override val store: Store = Store.ITCH
|
||||
|
||||
override fun signIn(context: Context) {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(ItchApi.KEYS_PAGE)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
}
|
||||
|
||||
override fun signOut(context: Context) {
|
||||
ItchPrefs.get(context).edit().remove("library_cache").remove("library_synced_at").apply()
|
||||
StoresState.post {
|
||||
StoresState.library.remove(store)
|
||||
StoresState.shelves.remove(store)
|
||||
StoresState.found.remove(store)
|
||||
StoresState.problems.remove(store)
|
||||
StoresState.status.remove(store)
|
||||
}
|
||||
}
|
||||
|
||||
override fun syncLibrary(context: Context, force: Boolean) {
|
||||
val app = context.applicationContext
|
||||
Thread({ ItchLibrary.sync(app, force) }, "itch-sync").start()
|
||||
}
|
||||
|
||||
override fun loadShelves(context: Context, force: Boolean) {
|
||||
val app = context.applicationContext
|
||||
ItchShelves.cached(app)?.let { cached -> StoresState.post { StoresState.shelves[store] = cached } }
|
||||
Thread({
|
||||
val shelves = ItchShelves.featured(app, force)
|
||||
if (shelves != null) {
|
||||
StoresState.post { StoresState.shelves[store] = shelves }
|
||||
} else {
|
||||
StoresState.post { StoresState.problems[store] = app.getString(R.string.stores_itch_no_catalog) }
|
||||
}
|
||||
}, "itch-shelves").start()
|
||||
}
|
||||
|
||||
override fun search(context: Context, query: String): List<CatalogItem> = ItchShelves.search(context, query)
|
||||
|
||||
override fun install(context: Context, item: CatalogItem, root: File) {
|
||||
val app = context.applicationContext
|
||||
if (item.extra["downloadKeyId"].isNullOrEmpty()) {
|
||||
StoresState.logLine("itch.io: \"${item.title}\" is not in the library")
|
||||
return
|
||||
}
|
||||
val folder = StoreInstallRoot.folderFor(app, Store.ITCH, item.id, item.title, root)
|
||||
val entry = DownloadEntry(
|
||||
Store.ITCH, item.id, item.title, cover = item.imageUrl, bytesTotal = StoreSizes.size(item),
|
||||
location = StoreInstallRoot.labelFor(app, folder),
|
||||
)
|
||||
DownloadQueue.enqueue(app, entry) { ItchInstaller(app, item, folder) }
|
||||
}
|
||||
|
||||
override fun uninstall(context: Context, game: InstalledStoreGame) {}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package com.droiddeck.launcher.stores.itch
|
||||
|
||||
import com.droiddeck.launcher.stores.CatalogItem
|
||||
import com.droiddeck.launcher.stores.Store
|
||||
import com.droiddeck.launcher.stores.StoreShelves
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
internal object ItchCatalog {
|
||||
fun slug(url: String): String = url.substringAfter("://", url).trimEnd('/')
|
||||
|
||||
fun shelvesJson(shelves: StoreShelves): String = JSONObject()
|
||||
.put("new", array(shelves.whatsNew))
|
||||
.put("deals", array(shelves.deals))
|
||||
.put("free", array(shelves.free))
|
||||
.put("top", array(shelves.trending))
|
||||
.toString()
|
||||
|
||||
fun shelves(raw: String?): StoreShelves? {
|
||||
if (raw.isNullOrBlank()) return null
|
||||
val o = runCatching { JSONObject(raw) }.getOrNull() ?: return null
|
||||
val shelves = StoreShelves(
|
||||
whatsNew = list(o.optJSONArray("new")),
|
||||
deals = list(o.optJSONArray("deals")),
|
||||
free = list(o.optJSONArray("free")),
|
||||
trending = list(o.optJSONArray("top")),
|
||||
)
|
||||
return shelves.takeUnless { it.isEmpty }
|
||||
}
|
||||
|
||||
fun array(items: List<CatalogItem>): JSONArray {
|
||||
val out = JSONArray()
|
||||
items.forEach { out.put(json(it)) }
|
||||
return out
|
||||
}
|
||||
|
||||
fun list(raw: JSONArray?): List<CatalogItem> {
|
||||
if (raw == null) return emptyList()
|
||||
return (0 until raw.length()).mapNotNull { raw.optJSONObject(it)?.let(::item) }
|
||||
}
|
||||
|
||||
private fun json(i: CatalogItem): JSONObject = JSONObject()
|
||||
.put("id", i.id)
|
||||
.put("title", i.title)
|
||||
.put("image", i.imageUrl ?: "")
|
||||
.put("tall", i.tallImageUrl ?: "")
|
||||
.put("tags", i.tags)
|
||||
.put("free", i.isFree)
|
||||
.put("hasPrice", i.hasPrice)
|
||||
.put("finalPrice", i.finalPrice)
|
||||
.put("originalPrice", i.originalPrice)
|
||||
.put("discount", i.discountPercent)
|
||||
.put("url", i.storeUrl)
|
||||
.put("developer", i.developer)
|
||||
.put("publisher", i.publisher)
|
||||
.put("releaseDate", i.releaseDate)
|
||||
.put("description", i.description)
|
||||
.put("owned", i.owned)
|
||||
.put("size", i.sizeBytes)
|
||||
.put("extra", JSONObject(i.extra))
|
||||
.put("mature", i.mature)
|
||||
|
||||
private fun item(o: JSONObject): CatalogItem {
|
||||
val extra = o.optJSONObject("extra")
|
||||
val map = HashMap<String, String>()
|
||||
extra?.keys()?.forEach { k -> map[k] = extra.optString(k) }
|
||||
return CatalogItem(
|
||||
store = Store.ITCH,
|
||||
id = o.optString("id"),
|
||||
title = o.optString("title"),
|
||||
imageUrl = o.optString("image").ifBlank { null },
|
||||
tallImageUrl = o.optString("tall").ifBlank { null },
|
||||
tags = o.optString("tags"),
|
||||
isFree = o.optBoolean("free"),
|
||||
hasPrice = o.optBoolean("hasPrice"),
|
||||
finalPrice = o.optString("finalPrice"),
|
||||
originalPrice = o.optString("originalPrice"),
|
||||
discountPercent = o.optInt("discount"),
|
||||
storeUrl = o.optString("url"),
|
||||
developer = o.optString("developer"),
|
||||
publisher = o.optString("publisher"),
|
||||
releaseDate = o.optString("releaseDate"),
|
||||
description = o.optString("description"),
|
||||
owned = o.optBoolean("owned"),
|
||||
sizeBytes = o.optLong("size"),
|
||||
extra = map,
|
||||
mature = o.optBoolean("mature"),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,372 +0,0 @@
|
||||
package com.droiddeck.launcher.stores.itch
|
||||
|
||||
import android.content.Context
|
||||
import com.droiddeck.launcher.R
|
||||
import com.droiddeck.launcher.runtime.GuestCommand
|
||||
import com.droiddeck.launcher.runtime.LinuxRuntime
|
||||
import com.droiddeck.launcher.stores.CatalogItem
|
||||
import com.droiddeck.launcher.stores.Store
|
||||
import com.droiddeck.launcher.stores.StoreExe
|
||||
import com.droiddeck.launcher.stores.StoreGameSidecar
|
||||
import com.droiddeck.launcher.stores.StoreInstallRoot
|
||||
import com.droiddeck.launcher.stores.StoreInstalls
|
||||
import com.droiddeck.launcher.stores.StoreNet
|
||||
import com.droiddeck.launcher.stores.download.DownloadQueue
|
||||
import com.droiddeck.launcher.stores.download.DownloadStage
|
||||
import org.apache.commons.compress.archivers.sevenz.SevenZFile
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile
|
||||
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream
|
||||
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
internal class ItchInstaller(
|
||||
private val app: Context,
|
||||
private val item: CatalogItem,
|
||||
private val folder: File,
|
||||
) : DownloadQueue.DownloadJob {
|
||||
private val cancelled = AtomicBoolean(false)
|
||||
|
||||
override fun run(handle: DownloadQueue.JobHandle): String? {
|
||||
val key = ItchAuth.key(app)
|
||||
if (key.isBlank()) throw IllegalStateException(app.getString(R.string.stores_sync_sign_in))
|
||||
val downloadKeyId = item.extra["downloadKeyId"].orEmpty()
|
||||
if (downloadKeyId.isBlank()) throw IllegalStateException(app.getString(R.string.stores_itch_not_owned))
|
||||
val gameId = item.extra["gameId"].orEmpty().ifBlank { item.id.substringAfterLast('/') }
|
||||
StoreInstalls.begin(folder, Store.ITCH, item.id, item.title, item.imageUrl, item.imageUrl)
|
||||
handle.stage(DownloadStage.MANIFEST)
|
||||
val upload = ItchUploads.windows(ItchApi.uploads(key, gameId, downloadKeyId))
|
||||
?: throw IllegalStateException(app.getString(R.string.stores_itch_no_upload))
|
||||
val uploadId = upload.optString("id")
|
||||
val filename = ItchUploads.filename(upload)
|
||||
val size = ItchUploads.size(upload)
|
||||
val scratch = StoreInstallRoot.scratchDir(app, Store.ITCH, item.id)
|
||||
scratch.mkdirs()
|
||||
val archive = File(scratch, filename.ifBlank { "download.bin" })
|
||||
val exe = try {
|
||||
handle.stage(DownloadStage.DOWNLOAD)
|
||||
download(key, downloadKeyId, uploadId, upload.optString("updated_at", upload.optString("updatedAt")), archive, size, handle)
|
||||
if (cancelled.get()) return null
|
||||
handle.stage(DownloadStage.INSTALL)
|
||||
install(archive, filename, handle)
|
||||
} catch (t: Throwable) {
|
||||
if (!cancelled.get()) StoreInstalls.discard(app, folder, listOf(scratch))
|
||||
throw t
|
||||
}
|
||||
if (exe == null) {
|
||||
if (cancelled.get()) return null
|
||||
StoreInstalls.discard(app, folder, listOf(scratch))
|
||||
throw IllegalStateException(app.getString(R.string.stores_itch_no_exe))
|
||||
}
|
||||
archive.delete()
|
||||
StoreInstalls.deleteTree(scratch)
|
||||
val sidecar = StoreGameSidecar(
|
||||
store = Store.ITCH,
|
||||
id = item.id,
|
||||
title = item.title,
|
||||
exe = exe,
|
||||
installVersion = upload.optString("updated_at", upload.optString("updatedAt")),
|
||||
installedAt = System.currentTimeMillis(),
|
||||
cover = item.imageUrl,
|
||||
hero = item.imageUrl,
|
||||
extra = mapOf("gameId" to gameId, "downloadKeyId" to downloadKeyId, "uploadId" to uploadId),
|
||||
cloud = false,
|
||||
)
|
||||
StoreInstalls.complete(app, folder, sidecar) { done, steps -> handle.stageProgress(done.toLong(), steps.toLong()) }
|
||||
return folder.path
|
||||
}
|
||||
|
||||
override fun cancel(deleteFiles: Boolean) {
|
||||
cancelled.set(true)
|
||||
if (deleteFiles) StoreInstalls.discard(app, folder, listOf(StoreInstallRoot.scratchDir(app, Store.ITCH, item.id)))
|
||||
}
|
||||
|
||||
private fun download(key: String, downloadKeyId: String, uploadId: String, version: String, target: File, expected: Long,
|
||||
handle: DownloadQueue.JobHandle) {
|
||||
// A partial file is only resumed when it came from this exact upload build.
|
||||
val origin = File(target.path + ".from")
|
||||
val stamp = "$uploadId $version $expected"
|
||||
if (!target.isFile || runCatching { origin.readText() }.getOrNull() != stamp) {
|
||||
target.delete()
|
||||
origin.writeText(stamp)
|
||||
}
|
||||
if (expected > 0 && target.length() == expected) {
|
||||
handle.progress(expected, expected)
|
||||
return
|
||||
}
|
||||
for (attempt in 0..1) {
|
||||
val have = target.length()
|
||||
var conn: HttpURLConnection? = null
|
||||
try {
|
||||
conn = (URL(ItchApi.downloadUrl(uploadId, downloadKeyId)).openConnection() as HttpURLConnection).apply {
|
||||
connectTimeout = 30_000
|
||||
readTimeout = 60_000
|
||||
instanceFollowRedirects = true
|
||||
setRequestProperty("User-Agent", StoreNet.BROWSER_UA)
|
||||
setRequestProperty("Authorization", "Bearer $key")
|
||||
setRequestProperty("Accept", "*/*")
|
||||
if (have > 0) setRequestProperty("Range", "bytes=$have-")
|
||||
}
|
||||
val code = conn.responseCode
|
||||
if (code == 416 && attempt == 0) {
|
||||
target.delete()
|
||||
continue
|
||||
}
|
||||
if (code !in 200..299) throw IllegalStateException(app.getString(R.string.stores_itch_download_failed, code))
|
||||
val append = code == 206 && have > 0
|
||||
if (have > 0) handle.log(if (append) "itch.io: resuming at ${have} bytes" else "itch.io: server ignored the resume, starting over")
|
||||
var done = if (append) have else 0L
|
||||
val total = conn.contentLengthLong.takeIf { it > 0 }?.let { if (append) it + have else it } ?: expected
|
||||
// The first report is the queue's speed baseline, so resumed bytes don't read as instant throughput.
|
||||
handle.progress(done, total)
|
||||
val buffer = ByteArray(128 * 1024)
|
||||
conn.inputStream.use { input ->
|
||||
FileOutputStream(target, append).use { out ->
|
||||
while (true) {
|
||||
if (cancelled.get()) return
|
||||
val read = input.read(buffer)
|
||||
if (read < 0) break
|
||||
out.write(buffer, 0, read)
|
||||
done += read
|
||||
handle.progress(done, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (total > 0 && done < total) throw IllegalStateException(app.getString(R.string.stores_itch_download_failed, code))
|
||||
return
|
||||
} finally {
|
||||
runCatching { conn?.disconnect() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun install(archive: File, filename: String, handle: DownloadQueue.JobHandle): String? {
|
||||
folder.mkdirs()
|
||||
when (val kind = sniff(archive, filename)) {
|
||||
"zip" -> unzip(archive, handle)
|
||||
"7z" -> sevenZip(archive, handle)
|
||||
"rar" -> unrar(archive, handle)
|
||||
"exe" -> copy(archive, File(folder, filename.ifBlank { "game.exe" }))
|
||||
"tar" -> untar(archive, null, handle)
|
||||
"gz", "bz2", "xz" -> untar(archive, kind, handle) ?: single(archive, kind, handle)
|
||||
else -> copy(archive, File(folder, filename.ifBlank { "game" }))
|
||||
}
|
||||
val hint = filename.substringBeforeLast('.')
|
||||
val exe = StoreExe.pick(folder, hint.ifBlank { null }, item.title)
|
||||
if (exe.isBlank()) {
|
||||
handle.log("itch.io: no exe found in ${folder.name}")
|
||||
return null
|
||||
}
|
||||
return exe
|
||||
}
|
||||
|
||||
private fun sniff(file: File, filename: String): String {
|
||||
val extension = filename.substringAfterLast('.', "").lowercase()
|
||||
if (extension == "exe") return "exe"
|
||||
val head = runCatching { FileInputStream(file).use { input -> ByteArray(300).also { input.read(it) } } }.getOrNull()
|
||||
?: return extension
|
||||
fun at(i: Int, vararg bytes: Int) = bytes.indices.all { head.getOrNull(i + it) == bytes[it].toByte() }
|
||||
return when {
|
||||
at(0, 0x50, 0x4B) -> "zip"
|
||||
at(0, 0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C) -> "7z"
|
||||
at(0, 0x52, 0x61, 0x72, 0x21) -> "rar"
|
||||
at(0, 0x1F, 0x8B) -> "gz"
|
||||
at(0, 0x42, 0x5A, 0x68) -> "bz2"
|
||||
at(0, 0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00) -> "xz"
|
||||
at(257, 0x75, 0x73, 0x74, 0x61, 0x72) -> "tar"
|
||||
else -> extension
|
||||
}
|
||||
}
|
||||
|
||||
private fun target(name: String): File? {
|
||||
val file = File(folder, name.replace('\\', '/'))
|
||||
val root = folder.canonicalPath
|
||||
val path = file.canonicalPath
|
||||
return if (path == root || path.startsWith(root + File.separator)) file else null
|
||||
}
|
||||
|
||||
// No JVM library reads RAR5; the runtime's bsdtar (libarchive) does.
|
||||
private fun unrar(archive: File, handle: DownloadQueue.JobHandle) {
|
||||
if (!LinuxRuntime.isInstalled(app)) throw IllegalStateException(app.getString(R.string.stores_itch_archive, "rar"))
|
||||
val binds = if (listOf(app.filesDir, app.cacheDir).any { folder.absolutePath.startsWith(it.absolutePath + "/") }) null
|
||||
else listOf(folder.absolutePath)
|
||||
fun bsdtar(args: List<String>, onLine: (String) -> Unit) =
|
||||
GuestCommand.run(app, listOf("/usr/bin/bsdtar") + args, logName = "itch-unrar", extraBinds = binds, fastPath = true) { line ->
|
||||
if (!line.startsWith("proot ")) onLine(line)
|
||||
}
|
||||
var entries = 0L
|
||||
bsdtar(listOf("-tf", archive.absolutePath)) { entries++ }
|
||||
var done = 0L
|
||||
val tail = ArrayDeque<String>()
|
||||
val code = bsdtar(listOf("-xvf", archive.absolutePath, "-C", folder.absolutePath)) { line ->
|
||||
if (line.startsWith("x ")) handle.stageProgress(++done, entries) else {
|
||||
if (tail.size == 5) tail.removeFirst()
|
||||
tail.addLast(line)
|
||||
}
|
||||
}
|
||||
if (cancelled.get()) return
|
||||
if (code != 0) {
|
||||
handle.log("itch.io: bsdtar exited $code: ${tail.joinToString(" | ")}")
|
||||
throw IllegalStateException(app.getString(R.string.stores_itch_extract_failed, code))
|
||||
}
|
||||
}
|
||||
|
||||
private fun sevenZip(archive: File, handle: DownloadQueue.JobHandle) {
|
||||
val total = archive.length()
|
||||
var written = 0L
|
||||
val buffer = ByteArray(64 * 1024)
|
||||
SevenZFile.builder().setFile(archive).get().use { seven ->
|
||||
while (true) {
|
||||
if (cancelled.get()) return
|
||||
val entry = seven.nextEntry ?: break
|
||||
val file = target(entry.name)
|
||||
if (file == null) {
|
||||
handle.log("itch.io: skipped an entry outside the game folder")
|
||||
continue
|
||||
}
|
||||
if (entry.isDirectory) {
|
||||
file.mkdirs()
|
||||
continue
|
||||
}
|
||||
file.parentFile?.mkdirs()
|
||||
FileOutputStream(file).use { out ->
|
||||
while (true) {
|
||||
if (cancelled.get()) return
|
||||
val read = seven.read(buffer, 0, buffer.size)
|
||||
if (read < 0) break
|
||||
out.write(buffer, 0, read)
|
||||
written += read
|
||||
handle.stageProgress(written, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun untar(archive: File, codec: String?, handle: DownloadQueue.JobHandle): Unit? {
|
||||
val total = archive.length()
|
||||
var written = 0L
|
||||
var entries = 0
|
||||
val buffer = ByteArray(64 * 1024)
|
||||
val opened = runCatching {
|
||||
FileInputStream(archive).use { raw ->
|
||||
val stream = when (codec) {
|
||||
"gz" -> GzipCompressorInputStream(BufferedInputStream(raw))
|
||||
"bz2" -> BZip2CompressorInputStream(BufferedInputStream(raw))
|
||||
"xz" -> XZCompressorInputStream(BufferedInputStream(raw))
|
||||
else -> BufferedInputStream(raw)
|
||||
}
|
||||
stream.use { input ->
|
||||
TarArchiveInputStream(input).use { tar ->
|
||||
while (true) {
|
||||
if (cancelled.get()) return null
|
||||
val entry = tar.nextEntry ?: break
|
||||
entries++
|
||||
val file = target(entry.name)
|
||||
if (file == null) {
|
||||
handle.log("itch.io: skipped an entry outside the game folder")
|
||||
continue
|
||||
}
|
||||
if (entry.isDirectory) {
|
||||
file.mkdirs()
|
||||
continue
|
||||
}
|
||||
file.parentFile?.mkdirs()
|
||||
FileOutputStream(file).use { out ->
|
||||
while (true) {
|
||||
if (cancelled.get()) return null
|
||||
val read = tar.read(buffer, 0, buffer.size)
|
||||
if (read < 0) break
|
||||
out.write(buffer, 0, read)
|
||||
written += read
|
||||
handle.stageProgress(written, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
if (opened.isSuccess && entries > 0) return Unit
|
||||
if (opened.isFailure && codec == null) throw opened.exceptionOrNull() ?: IllegalStateException("tar")
|
||||
return null
|
||||
}
|
||||
|
||||
private fun single(archive: File, codec: String, handle: DownloadQueue.JobHandle) {
|
||||
val name = archive.name.substringBeforeLast('.')
|
||||
val file = target(name.ifBlank { "game" }) ?: throw IllegalStateException("archive")
|
||||
file.parentFile?.mkdirs()
|
||||
val total = archive.length()
|
||||
var written = 0L
|
||||
val buffer = ByteArray(64 * 1024)
|
||||
FileInputStream(archive).use { raw ->
|
||||
val stream = when (codec) {
|
||||
"bz2" -> BZip2CompressorInputStream(BufferedInputStream(raw))
|
||||
"xz" -> XZCompressorInputStream(BufferedInputStream(raw))
|
||||
else -> GzipCompressorInputStream(BufferedInputStream(raw))
|
||||
}
|
||||
stream.use { input ->
|
||||
FileOutputStream(file).use { out ->
|
||||
while (true) {
|
||||
if (cancelled.get()) return
|
||||
val read = input.read(buffer)
|
||||
if (read < 0) break
|
||||
out.write(buffer, 0, read)
|
||||
written += read
|
||||
handle.stageProgress(written, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun copy(source: File, target: File) {
|
||||
target.parentFile?.mkdirs()
|
||||
FileInputStream(source).use { input -> FileOutputStream(target).use { out -> input.copyTo(out, 128 * 1024) } }
|
||||
}
|
||||
|
||||
// Streaming readers stop silently on zips whose data descriptors disagree with the local headers;
|
||||
// the central directory is authoritative.
|
||||
private fun unzip(archive: File, handle: DownloadQueue.JobHandle) {
|
||||
val total = archive.length()
|
||||
var written = 0L
|
||||
val buffer = ByteArray(64 * 1024)
|
||||
ZipFile.builder().setFile(archive).get().use { zip ->
|
||||
for (entry in zip.entries) {
|
||||
if (cancelled.get()) return
|
||||
val target = target(entry.name)
|
||||
if (target == null) {
|
||||
handle.log("itch.io: skipped an entry outside the game folder")
|
||||
continue
|
||||
}
|
||||
if (entry.isDirectory) {
|
||||
target.mkdirs()
|
||||
continue
|
||||
}
|
||||
target.parentFile?.mkdirs()
|
||||
zip.getInputStream(entry).use { input ->
|
||||
FileOutputStream(target).use { out ->
|
||||
while (true) {
|
||||
if (cancelled.get()) return
|
||||
val read = input.read(buffer)
|
||||
if (read < 0) break
|
||||
out.write(buffer, 0, read)
|
||||
written += read
|
||||
handle.stageProgress(written, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
package com.droiddeck.launcher.stores.itch
|
||||
|
||||
import android.content.Context
|
||||
import com.droiddeck.launcher.R
|
||||
import com.droiddeck.launcher.stores.CatalogItem
|
||||
import com.droiddeck.launcher.stores.Store
|
||||
import com.droiddeck.launcher.stores.StoresState
|
||||
import com.droiddeck.launcher.stores.cleanStoreText
|
||||
import com.droiddeck.launcher.stores.mergeItems
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
internal object ItchLibrary {
|
||||
private const val KEY_CACHE = "library_cache"
|
||||
private const val KEY_AT = "library_synced_at"
|
||||
private const val MAX_PAGES = 20
|
||||
private const val THROTTLE_MS = 6 * 60 * 60 * 1000L
|
||||
|
||||
private val running = AtomicBoolean(false)
|
||||
|
||||
fun cached(context: Context): List<CatalogItem> {
|
||||
val raw = ItchPrefs.get(context).getString(KEY_CACHE, null) ?: return emptyList()
|
||||
return runCatching { ItchCatalog.list(JSONArray(raw)) }.getOrDefault(emptyList())
|
||||
}
|
||||
|
||||
fun sync(context: Context, force: Boolean) {
|
||||
val app = context.applicationContext
|
||||
if (!running.compareAndSet(false, true)) return
|
||||
try {
|
||||
val key = ItchAuth.key(app)
|
||||
val cache = cached(app)
|
||||
val prefs = ItchPrefs.get(app)
|
||||
if (key.isBlank()) {
|
||||
StoresState.post {
|
||||
StoresState.status.remove(Store.ITCH)
|
||||
if (cache.isNotEmpty()) StoresState.library[Store.ITCH] = cache
|
||||
StoresState.problems[Store.ITCH] = app.getString(R.string.stores_sync_sign_in)
|
||||
}
|
||||
return
|
||||
}
|
||||
val quiet = cache.isNotEmpty() && !force
|
||||
if (quiet && System.currentTimeMillis() - prefs.getLong(KEY_AT, 0L) < THROTTLE_MS) {
|
||||
StoresState.post { StoresState.library[Store.ITCH] = cache }
|
||||
return
|
||||
}
|
||||
if (!quiet) StoresState.post { StoresState.status[Store.ITCH] = app.getString(R.string.stores_sync_game_list) }
|
||||
val items = fetch(key)
|
||||
if (items == null) {
|
||||
StoresState.post {
|
||||
StoresState.status.remove(Store.ITCH)
|
||||
if (cache.isNotEmpty()) StoresState.library[Store.ITCH] = cache
|
||||
StoresState.problems[Store.ITCH] = app.getString(R.string.stores_sync_failed)
|
||||
}
|
||||
return
|
||||
}
|
||||
prefs.edit()
|
||||
.putString(KEY_CACHE, ItchCatalog.array(items).toString())
|
||||
.putLong(KEY_AT, System.currentTimeMillis())
|
||||
.apply()
|
||||
StoresState.post {
|
||||
StoresState.status.remove(Store.ITCH)
|
||||
StoresState.problems.remove(Store.ITCH)
|
||||
StoresState.library[Store.ITCH] = mergeItems(StoresState.library[Store.ITCH] ?: cache, items)
|
||||
}
|
||||
StoresState.logLine("itch.io: ${items.size} owned games")
|
||||
} finally {
|
||||
running.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun size(context: Context, item: CatalogItem): Long {
|
||||
val key = ItchAuth.key(context.applicationContext)
|
||||
if (key.isBlank()) return 0L
|
||||
val downloadKeyId = item.extra["downloadKeyId"].orEmpty().ifEmpty { return 0L }
|
||||
val gameId = item.extra["gameId"].orEmpty().ifEmpty { return 0L }
|
||||
val upload = ItchUploads.windows(ItchApi.uploads(key, gameId, downloadKeyId)) ?: return 0L
|
||||
return ItchUploads.size(upload)
|
||||
}
|
||||
|
||||
private fun fetch(key: String): List<CatalogItem>? {
|
||||
val out = ArrayList<CatalogItem>()
|
||||
var page = 1
|
||||
var keys = 0
|
||||
while (page <= MAX_PAGES) {
|
||||
val body = ItchApi.ownedKeys(key, page) ?: return null
|
||||
val list = body.optJSONArray("owned_keys") ?: body.optJSONArray("ownedKeys") ?: return out
|
||||
if (list.length() == 0) break
|
||||
out.addAll(items(list))
|
||||
keys += list.length()
|
||||
page++
|
||||
}
|
||||
val games = out.distinctBy { it.id }
|
||||
StoresState.logLine("itch.io: $keys download keys, ${games.size} games")
|
||||
return games
|
||||
}
|
||||
|
||||
internal fun items(keys: JSONArray): List<CatalogItem> {
|
||||
val out = ArrayList<CatalogItem>(keys.length())
|
||||
for (i in 0 until keys.length()) {
|
||||
val entry = keys.optJSONObject(i) ?: continue
|
||||
val game = entry.optJSONObject("game") ?: continue
|
||||
item(game, entry.optString("id"))?.let { out.add(it) }
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
private fun JSONObject.text(snake: String, camel: String): String =
|
||||
optString(snake).ifBlank { optString(camel) }
|
||||
|
||||
internal fun item(game: JSONObject, downloadKeyId: String): CatalogItem? {
|
||||
val gameId = game.optLong("id")
|
||||
if (gameId <= 0L) return null
|
||||
val user = game.optJSONObject("user")
|
||||
val url = game.optString("url").ifBlank {
|
||||
val owner = user?.optString("username").orEmpty()
|
||||
if (owner.isBlank()) ItchApi.pageUrl(gameId.toString()) else ItchApi.pageUrl("$owner/$gameId")
|
||||
}
|
||||
val traits = game.optJSONArray("traits")?.let { arr ->
|
||||
(0 until arr.length()).map { arr.optString(it) }
|
||||
}.orEmpty()
|
||||
val platforms = game.optJSONObject("platforms")
|
||||
val sale = game.optJSONObject("sale")
|
||||
val rate = sale?.optDouble("rate", 0.0) ?: 0.0
|
||||
val cents = game.optLong("min_price", game.optLong("minPrice"))
|
||||
val canBuy = game.optBoolean("can_be_bought", game.optBoolean("canBeBought", "can_be_bought" in traits))
|
||||
val discount = if (rate > 0.0 && rate < 1.0) (rate * 100).roundToInt() else 0
|
||||
val price = if (canBuy && cents > 0) money(cents) else ""
|
||||
val salePrice = if (discount > 0 && price.isNotEmpty()) money((cents * (1.0 - rate)).roundToInt().toLong()) else ""
|
||||
val tags = buildList {
|
||||
if ("p_windows" in traits || platforms?.optBoolean("windows") == true) add("Windows")
|
||||
if ("p_linux" in traits || platforms?.optBoolean("linux") == true) add("Linux")
|
||||
if ("p_osx" in traits || platforms?.optBoolean("osx") == true) add("macOS")
|
||||
}.joinToString(", ")
|
||||
val cover = game.text("cover_url", "coverUrl").ifBlank { game.text("still_cover_url", "stillCoverUrl") }
|
||||
val tall = game.text("still_cover_url", "stillCoverUrl")
|
||||
val author = user?.text("display_name", "displayName").orEmpty().ifBlank { user?.optString("username").orEmpty() }
|
||||
return CatalogItem(
|
||||
store = Store.ITCH,
|
||||
id = ItchCatalog.slug(url).ifBlank { gameId.toString() },
|
||||
title = game.optString("title").ifBlank { "itch.io game $gameId" },
|
||||
imageUrl = cover.ifBlank { null },
|
||||
tallImageUrl = tall.ifBlank { null },
|
||||
tags = tags,
|
||||
isFree = price.isEmpty(),
|
||||
hasPrice = price.isNotEmpty(),
|
||||
finalPrice = salePrice.ifEmpty { price },
|
||||
originalPrice = if (salePrice.isNotEmpty()) price else "",
|
||||
discountPercent = if (salePrice.isNotEmpty()) discount else 0,
|
||||
storeUrl = url,
|
||||
developer = author,
|
||||
publisher = author,
|
||||
releaseDate = game.text("published_at", "publishedAt").take(10),
|
||||
description = cleanStoreText(game.text("short_text", "shortText")),
|
||||
owned = downloadKeyId.isNotBlank(),
|
||||
extra = if (downloadKeyId.isBlank()) emptyMap() else mapOf("downloadKeyId" to downloadKeyId, "gameId" to gameId.toString()),
|
||||
)
|
||||
}
|
||||
|
||||
private fun money(cents: Long): String = "$" + (cents / 100) + "." + (cents % 100).toString().padStart(2, '0')
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.droiddeck.launcher.stores.itch
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
|
||||
object ItchPrefs {
|
||||
const val FILE = "stores.itch"
|
||||
|
||||
@JvmStatic
|
||||
fun get(context: Context): SharedPreferences = context.applicationContext.getSharedPreferences(FILE, Context.MODE_PRIVATE)
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
package com.droiddeck.launcher.stores.itch
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import android.util.Xml
|
||||
import com.droiddeck.launcher.stores.CatalogItem
|
||||
import com.droiddeck.launcher.stores.Store
|
||||
import com.droiddeck.launcher.stores.StoreNet
|
||||
import com.droiddeck.launcher.stores.StoreShelves
|
||||
import com.droiddeck.launcher.stores.cleanStoreText
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import java.io.StringReader
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
internal object ItchShelves {
|
||||
private const val TAG = "ItchShelves"
|
||||
private const val KEY = "shelves_cache"
|
||||
private const val KEY_AT = "shelves_at"
|
||||
private const val FRESH_MS = 3 * 60 * 60 * 1000L
|
||||
private const val GAP_MS = 1_200L
|
||||
private const val RETRY_MS = 20_000L
|
||||
|
||||
private val FEEDS = listOf(
|
||||
"https://itch.io/games/newest.xml",
|
||||
"https://itch.io/games/on-sale.xml",
|
||||
"https://itch.io/games/price-free.xml",
|
||||
"https://itch.io/games/top-rated.xml",
|
||||
)
|
||||
private val PLATFORMS = setOf("windows", "linux", "osx", "mac", "android", "web", "vr", "mobile")
|
||||
|
||||
@Volatile
|
||||
private var memory: StoreShelves? = null
|
||||
|
||||
fun cached(context: Context): StoreShelves? {
|
||||
memory?.let { return it }
|
||||
val stored = stored(ItchPrefs.get(context.applicationContext))
|
||||
if (stored != null) memory = stored
|
||||
return stored
|
||||
}
|
||||
|
||||
fun featured(context: Context, force: Boolean): StoreShelves? {
|
||||
val prefs = ItchPrefs.get(context.applicationContext)
|
||||
val fresh = System.currentTimeMillis() - prefs.getLong(KEY_AT, 0L) < FRESH_MS
|
||||
if (fresh && !force) {
|
||||
cached(context)?.let { return it }
|
||||
}
|
||||
val fetched = fetch()
|
||||
if (fetched != null) {
|
||||
memory = fetched
|
||||
prefs.edit().putString(KEY, ItchCatalog.shelvesJson(fetched)).putLong(KEY_AT, System.currentTimeMillis()).apply()
|
||||
return fetched
|
||||
}
|
||||
return cached(context)
|
||||
}
|
||||
|
||||
fun search(context: Context, query: String): List<CatalogItem> {
|
||||
val key = ItchAuth.key(context.applicationContext)
|
||||
if (key.isBlank()) return emptyList()
|
||||
val out = ArrayList<CatalogItem>()
|
||||
for (page in 1..2) {
|
||||
val games = ItchApi.search(key, query, page) ?: break
|
||||
if (games.length() == 0) break
|
||||
for (i in 0 until games.length()) {
|
||||
val game = games.optJSONObject(i) ?: continue
|
||||
ItchLibrary.item(game, "")?.let { out.add(it) }
|
||||
}
|
||||
if (games.length() < 30) break
|
||||
Thread.sleep(GAP_MS)
|
||||
}
|
||||
return out.distinctBy { it.id }
|
||||
}
|
||||
|
||||
private fun stored(prefs: SharedPreferences): StoreShelves? = ItchCatalog.shelves(prefs.getString(KEY, null))
|
||||
|
||||
private fun fetch(): StoreShelves? {
|
||||
val lists = ArrayList<List<CatalogItem>>(FEEDS.size)
|
||||
var answered = false
|
||||
for ((index, url) in FEEDS.withIndex()) {
|
||||
if (index > 0) Thread.sleep(GAP_MS)
|
||||
var (code, body) = open(url)
|
||||
if (code == 429) {
|
||||
Log.w(TAG, "feed 429; backing off")
|
||||
Thread.sleep(RETRY_MS)
|
||||
val again = open(url)
|
||||
code = again.first
|
||||
body = again.second
|
||||
if (code == 429) {
|
||||
Log.w(TAG, "feed still 429; keeping the cached shelves")
|
||||
lists.add(emptyList())
|
||||
break
|
||||
}
|
||||
}
|
||||
if (code in 200..299 && body != null) {
|
||||
answered = true
|
||||
lists.add(parse(body))
|
||||
} else {
|
||||
lists.add(emptyList())
|
||||
}
|
||||
}
|
||||
if (!answered) return null
|
||||
while (lists.size < FEEDS.size) lists.add(emptyList())
|
||||
val art = (lists[0] + lists[3]).mapNotNull { it.imageUrl }.distinct().take(12)
|
||||
val shelves = StoreShelves(lists[0], lists[1], lists[2], lists[3], art)
|
||||
return shelves.takeUnless { it.isEmpty }
|
||||
}
|
||||
|
||||
private fun open(url: String): Pair<Int, String?> {
|
||||
var conn: HttpURLConnection? = null
|
||||
return try {
|
||||
conn = (URL(url).openConnection() as HttpURLConnection).apply {
|
||||
connectTimeout = 20_000
|
||||
readTimeout = 20_000
|
||||
instanceFollowRedirects = true
|
||||
setRequestProperty("User-Agent", StoreNet.BROWSER_UA)
|
||||
setRequestProperty("Accept", "application/xml, text/xml, application/rss+xml, */*")
|
||||
}
|
||||
val code = conn.responseCode
|
||||
code to if (code in 200..299) conn.inputStream.bufferedReader().use { it.readText() } else null
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "feed ${e.javaClass.simpleName}")
|
||||
0 to null
|
||||
} finally {
|
||||
runCatching { conn?.disconnect() }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun parse(xml: String): List<CatalogItem> {
|
||||
val out = ArrayList<CatalogItem>()
|
||||
val parser = Xml.newPullParser()
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||
parser.setInput(StringReader(xml))
|
||||
var event = parser.eventType
|
||||
var field: String? = null
|
||||
var current: HashMap<String, String>? = null
|
||||
while (event != XmlPullParser.END_DOCUMENT) {
|
||||
when (event) {
|
||||
XmlPullParser.START_TAG -> {
|
||||
if (parser.name == "item") current = HashMap()
|
||||
field = parser.name
|
||||
}
|
||||
XmlPullParser.TEXT -> {
|
||||
val map = current
|
||||
val name = field
|
||||
if (map != null && name != null) map[name] = (map[name] ?: "") + parser.text
|
||||
}
|
||||
XmlPullParser.END_TAG -> {
|
||||
if (parser.name == "item") {
|
||||
current?.let { item(it)?.let(out::add) }
|
||||
current = null
|
||||
}
|
||||
field = null
|
||||
}
|
||||
}
|
||||
event = parser.next()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
internal fun item(m: Map<String, String>): CatalogItem? {
|
||||
val link = m["link"]?.trim().orEmpty().ifBlank { m["guid"]?.trim().orEmpty() }
|
||||
if (link.isBlank()) return null
|
||||
val title = m["plainTitle"]?.trim().orEmpty()
|
||||
.ifBlank { m["title"].orEmpty().substringBefore(" [").trim() }
|
||||
.ifBlank { ItchCatalog.slug(link) }
|
||||
val price = m["price"]?.trim().orEmpty()
|
||||
val free = price.isBlank() || price == "$0.00" || price == "0.00" || price.equals("free", ignoreCase = true)
|
||||
val tags = Regex("\\[([^]]+)]").findAll(m["title"].orEmpty())
|
||||
.map { it.groupValues[1].trim() }
|
||||
.filterNot { it.endsWith("% Off") || it.startsWith("$") || it.equals("Free", true) || it.lowercase() in PLATFORMS }
|
||||
.joinToString(", ")
|
||||
return CatalogItem(
|
||||
store = Store.ITCH,
|
||||
id = ItchCatalog.slug(link),
|
||||
title = title,
|
||||
imageUrl = m["imageurl"]?.trim()?.ifBlank { null },
|
||||
tags = tags,
|
||||
isFree = free,
|
||||
hasPrice = !free,
|
||||
finalPrice = if (free) "" else price,
|
||||
originalPrice = if (free) "" else m["fullPrice"]?.trim().orEmpty(),
|
||||
discountPercent = m["discountpercent"]?.trim()?.toIntOrNull() ?: 0,
|
||||
storeUrl = link,
|
||||
releaseDate = date(m["pubDate"]),
|
||||
description = cleanStoreText(m["description"]),
|
||||
)
|
||||
}
|
||||
|
||||
private fun date(raw: String?): String {
|
||||
val text = raw?.trim().orEmpty()
|
||||
if (text.isBlank()) return ""
|
||||
return runCatching {
|
||||
SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US).parse(text)
|
||||
}.getOrNull()?.let { SimpleDateFormat("yyyy-MM-dd", Locale.US).format(it) }.orEmpty()
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.droiddeck.launcher.stores.itch
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
internal object ItchUploads {
|
||||
private val GAME_TYPES = setOf("default", "flash", "unity", "java", "html", "")
|
||||
private val PLATFORM_TRAITS = setOf("p_windows", "p_linux", "p_osx", "p_android")
|
||||
|
||||
fun windows(uploads: JSONArray?): JSONObject? {
|
||||
if (uploads == null) return null
|
||||
val all = (0 until uploads.length()).mapNotNull { uploads.optJSONObject(it) }
|
||||
.filter { it.optString("storage") != "external" && !it.optBoolean("demo") }
|
||||
if (all.isEmpty()) return null
|
||||
val games = all.filter { it.optString("type") in GAME_TYPES }
|
||||
val preferred = games.ifEmpty { all }
|
||||
val candidates = preferred.filter { windows(it) }.ifEmpty { preferred }
|
||||
return candidates.maxWithOrNull(compareBy({ it.optString("updated_at", it.optString("updatedAt")) }, { it.optLong("id") }))
|
||||
}
|
||||
|
||||
fun windows(upload: JSONObject): Boolean {
|
||||
val platforms = upload.optJSONObject("platforms")
|
||||
if (platforms != null) {
|
||||
if (platforms.optBoolean("windows")) return true
|
||||
if (platforms.optBoolean("linux") || platforms.optBoolean("osx") || platforms.optBoolean("android")) return false
|
||||
}
|
||||
val traits = upload.optJSONArray("traits") ?: return true
|
||||
val names = (0 until traits.length()).map { traits.optString(it) }
|
||||
if (names.any { it == "p_windows" }) return true
|
||||
return names.none { it in PLATFORM_TRAITS }
|
||||
}
|
||||
|
||||
fun size(upload: JSONObject): Long = upload.optLong("size", upload.optLong("size_bytes"))
|
||||
|
||||
fun filename(upload: JSONObject): String = upload.optString("filename").substringAfterLast('/')
|
||||
}
|
||||
@@ -27,7 +27,7 @@ internal fun FrameGenMenu(
|
||||
onPick(m); host.open = null
|
||||
}
|
||||
item(FrameGen.Mode.OFF, focus = firstItemFocus)
|
||||
for (m in 2..4) item(FrameGen.Mode(FrameGen.ENGINE_DDFG, m))
|
||||
for (m in 2..4) item(FrameGen.Mode(FrameGen.ENGINE_WINFG, m))
|
||||
val lsfg = (2..4).map { FrameGen.Mode(FrameGen.ENGINE_LSFG, it) } +
|
||||
FrameGen.ADAPTIVE_TARGETS.map { FrameGen.Mode(FrameGen.ENGINE_LSFG, target = it) }
|
||||
lsfg.forEachIndexed { i, m -> item(m, enabled = lossless.ready, detail = need.takeIf { i == 0 }) }
|
||||
|
||||
@@ -138,7 +138,6 @@ class FrontEndState(
|
||||
val downloadMirror: String = "",
|
||||
val downloadPrefix: String = "",
|
||||
val animationsEnabled: Boolean = true,
|
||||
val launcherIcon: String = com.droiddeck.launcher.LauncherIcon.DARK,
|
||||
val gameStoresEnabled: Boolean = true,
|
||||
val gameStoresSpeedTier: String = "fast",
|
||||
val storesOpenTab: String = SessionPrefs.STORES_OPEN_LIBRARY,
|
||||
@@ -206,7 +205,6 @@ class FrontEndActions(
|
||||
val onTheme: (String) -> Unit = {},
|
||||
val onLanguage: (String) -> Unit = {},
|
||||
val onAppScale: (Int) -> Unit = {},
|
||||
val onLauncherIcon: (String) -> Unit = {},
|
||||
val onOrientation: (Int) -> Unit = {},
|
||||
val onLauncherFullscreen: (Boolean) -> Unit = {},
|
||||
val onAnimationsEnabled: (Boolean) -> Unit = {},
|
||||
@@ -388,7 +386,6 @@ private fun FrontEndScreenBody(s: FrontEndState, a: FrontEndActions, page: (@Com
|
||||
when (target) {
|
||||
"stores:downloads" -> { com.droiddeck.launcher.stores.StoresState.openDownloads = true; selected = "stores" }
|
||||
"components" -> a.onComponents(true)
|
||||
"setup:launcher" -> { SetupTabs.openLauncher = true; selected = "setup" }
|
||||
else -> selected = target
|
||||
}
|
||||
a.onNavigated()
|
||||
|
||||
@@ -86,11 +86,6 @@ import com.droiddeck.launcher.R
|
||||
private enum class CheckState { OK, WARN, BUSY }
|
||||
|
||||
private const val STORAGE_TAB = 3
|
||||
private const val LAUNCHER_TAB = 2
|
||||
|
||||
internal object SetupTabs {
|
||||
var openLauncher = false
|
||||
}
|
||||
private const val GLASSES_TAB = 5
|
||||
|
||||
@Composable
|
||||
@@ -182,8 +177,7 @@ internal fun SetupPanel(
|
||||
onSave = { a.onDownloadPrefix(it); editDownloadPrefix = false },
|
||||
)
|
||||
val tabs = listOf(stringResource(R.string.setup_tab_overview), stringResource(R.string.setup_tab_controller), stringResource(R.string.setup_tab_launcher), stringResource(R.string.setup_tab_storage), stringResource(R.string.setup_tab_diagnostics), stringResource(R.string.setup_tab_glasses))
|
||||
var tab by rememberSaveable { mutableStateOf(if (SetupTabs.openLauncher) LAUNCHER_TAB else 0) }
|
||||
SetupTabs.openLauncher = false
|
||||
var tab by rememberSaveable { mutableStateOf(0) }
|
||||
val chipFocus = remember { List(tabs.size) { FocusRequester() } }
|
||||
var tabTurned by remember { mutableStateOf(false) }
|
||||
val pick: (Int) -> Unit = { i -> tab = i; tabTurned = true }
|
||||
@@ -368,11 +362,6 @@ internal fun SetupPanel(
|
||||
)
|
||||
}, s.appScale, onPick = a.onAppScale,
|
||||
)
|
||||
ChoiceRow(
|
||||
host, "launcher-icon", stringResource(R.string.setup_launcher_icon), null,
|
||||
com.droiddeck.launcher.LauncherIcon.options.map { (style, label) -> style to stringResource(label) },
|
||||
s.launcherIcon, onPick = a.onLauncherIcon,
|
||||
)
|
||||
ToggleRow(host, "launcher-animations", stringResource(R.string.setup_animations), null,
|
||||
s.animationsEnabled, onChange = a.onAnimationsEnabled)
|
||||
LanguageRow(host, s.language, a.onLanguage)
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.droiddeck.launcher.R
|
||||
import com.droiddeck.launcher.stores.Store
|
||||
import com.droiddeck.launcher.stores.StoresState
|
||||
import com.droiddeck.launcher.stores.itch.ItchApi
|
||||
import com.droiddeck.launcher.stores.itch.ItchAuth
|
||||
import com.droiddeck.launcher.stores.itch.ItchLibrary
|
||||
|
||||
private fun copiedKey(ctx: Context): String? {
|
||||
val clipboard = ctx.getSystemService(ClipboardManager::class.java)
|
||||
val text = runCatching { clipboard?.primaryClip?.getItemAt(0)?.text?.toString() }.getOrNull() ?: return null
|
||||
return ItchAuth.candidates(text).firstOrNull()
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ItchKeyDialog(onDismiss: () -> Unit) {
|
||||
val ctx = LocalContext.current
|
||||
var key by remember { mutableStateOf(copiedKey(ctx).orEmpty()) }
|
||||
var busy by remember { mutableStateOf(false) }
|
||||
var rejected by remember { mutableStateOf(false) }
|
||||
|
||||
val lifecycle = androidx.compose.ui.platform.LocalLifecycleOwner.current.lifecycle
|
||||
DisposableEffect(lifecycle) {
|
||||
val observer = androidx.lifecycle.LifecycleEventObserver { _, event ->
|
||||
if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME && key.isBlank()) {
|
||||
copiedKey(ctx)?.let { key = it }
|
||||
}
|
||||
}
|
||||
lifecycle.addObserver(observer)
|
||||
onDispose { lifecycle.removeObserver(observer) }
|
||||
}
|
||||
|
||||
fun submit() {
|
||||
val raw = key.trim()
|
||||
if (raw.isEmpty() || busy) return
|
||||
busy = true
|
||||
rejected = false
|
||||
Thread({
|
||||
val user = ItchAuth.verify(raw)
|
||||
if (user == null) {
|
||||
StoresState.post { busy = false; rejected = true }
|
||||
return@Thread
|
||||
}
|
||||
ItchAuth.save(ctx, raw, user)
|
||||
StoresState.post { busy = false; onDismiss() }
|
||||
StoresState.refresh(ctx)
|
||||
ItchLibrary.sync(ctx, true)
|
||||
}, "itch-key").start()
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { if (!busy) onDismiss() },
|
||||
title = { Text(Store.ITCH.label) },
|
||||
text = {
|
||||
Column {
|
||||
AdbTextField(
|
||||
value = key,
|
||||
onValueChange = { key = it; rejected = false },
|
||||
label = stringResource(R.string.stores_itch_key),
|
||||
keyboardType = KeyboardType.Ascii,
|
||||
imeAction = ImeAction.Done,
|
||||
focusOnShow = true,
|
||||
onDone = { submit() },
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.Start) {
|
||||
TextButton(onClick = {
|
||||
ctx.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(ItchApi.KEYS_PAGE)))
|
||||
}) { Text(stringResource(R.string.stores_itch_get_key)) }
|
||||
}
|
||||
if (rejected) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
stringResource(R.string.stores_itch_key_rejected),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(enabled = key.isNotBlank() && !busy, onClick = { submit() }) {
|
||||
Text(stringResource(R.string.game_env_save))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(enabled = !busy, onClick = onDismiss) { Text(stringResource(R.string.common_cancel)) }
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -29,7 +29,6 @@ internal fun sourceColours(source: String): SourceColours = when (source) {
|
||||
Store.GOG.id -> SourceColours(Color(0xFF3A1A3C), Color(0xFFE6A3EA), Color(0xFFC25BC8))
|
||||
Store.EPIC.id -> SourceColours(Color(0xFF2A2A2A), Color(0xFFDDDDDD), Color(0xFFBDBDBD))
|
||||
Store.AMAZON.id -> SourceColours(Color(0xFF3A2A0A), Color(0xFFFFC266), Color(0xFFFF9900))
|
||||
Store.ITCH.id -> SourceColours(Color(0xFF3A1A1E), Color(0xFFFFB3B3), Color(0xFFFF5C5C))
|
||||
Library.SOURCE_STEAM -> SourceColours(Color(0xFF16293D), Color(0xFF7CC4FF), Color(0xFF1A9FFF))
|
||||
FLATHUB -> SourceColours(Color(0xFF0B2E2B), Color(0xFF9FE3DD), FlathubTeal)
|
||||
else -> SourceColours(Color(0xFF1A1D22), Color(0xFF9AA3AF), Color(0xFF9AA3AF))
|
||||
@@ -41,7 +40,6 @@ internal fun sourceLabel(source: String): String = when (source) {
|
||||
Store.GOG.id -> Store.GOG.label
|
||||
Store.EPIC.id -> Store.EPIC.label
|
||||
Store.AMAZON.id -> Store.AMAZON.label
|
||||
Store.ITCH.id -> Store.ITCH.label
|
||||
else -> stringResource(R.string.source_added)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
// `split` is the size of a leading section that starts its own rows; 0 means one continuous grid.
|
||||
internal fun storeGridNeighbor(index: Int, count: Int, columns: Int, direction: Int, split: Int = 0): Int? {
|
||||
internal fun storeGridNeighbor(index: Int, count: Int, columns: Int, direction: Int): Int? {
|
||||
if (index !in 0 until count || columns < 1 || direction !in listOf(-1, 1)) return null
|
||||
if (split !in 1 until count) return (index + columns * direction).takeIf { it in 0 until count }
|
||||
val inFirst = index < split
|
||||
val start = if (inFirst) 0 else split
|
||||
val end = if (inFirst) split else count
|
||||
val local = index - start
|
||||
val within = start + local + columns * direction
|
||||
if (within in start until end) return within
|
||||
val column = local % columns
|
||||
return when {
|
||||
inFirst && direction > 0 -> split + minOf(column, count - split - 1)
|
||||
!inFirst && direction < 0 && local < columns -> ((split - 1) / columns) * columns + column.coerceAtMost((split - 1) % columns)
|
||||
else -> null
|
||||
}
|
||||
return (index + columns * direction).takeIf { it in 0 until count }
|
||||
}
|
||||
@@ -23,8 +23,6 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -117,13 +115,10 @@ internal fun StoreGameDetail(store: Store, key: String, s: FrontEndState, a: Fro
|
||||
Actions {
|
||||
when {
|
||||
installed != null -> {
|
||||
val gone = StoresState.uninstalling["${store.id}:$id"]
|
||||
PrimaryButton(stringResource(R.string.stores_play), enabled = !s.busy && gone == null && download == null, main = true, icon = Icons.Filled.PlayArrow, modifier = leadOn) {
|
||||
launchStoreGame(ctx, store, id, s, a)
|
||||
}
|
||||
ActionChip(stringResource(R.string.stores_in_steam), ok = true)
|
||||
val gone = StoresState.uninstalling["${store.id}:$id"]
|
||||
if (gone != null) SecondaryButton(stringResource(R.string.stores_uninstalling), enabled = false, progress = gone) {}
|
||||
else UninstallButton(title, installed, enabled = download == null) {
|
||||
else UninstallButton(title, installed, enabled = download == null, modifier = leadOn) {
|
||||
StoresState.uninstall(ctx, installed) { StoresState.refresh(ctx); a.onLibraryChanged() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,10 @@ private fun DownloadList(s: FrontEndState, a: FrontEndActions) {
|
||||
private fun InterruptedRow(item: com.droiddeck.launcher.stores.CatalogItem) {
|
||||
val ctx = LocalContext.current
|
||||
ArtStrip(item.imageUrl ?: item.tallImageUrl, item.title) {
|
||||
StripLabel(item.title, item.store.id, stringResource(R.string.stores_dl_interrupted), Color(0xFFFFC24D))
|
||||
Text(item.title, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = Color.White, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f, fill = false))
|
||||
SourceChip(item.store.id, small = true)
|
||||
Text(stringResource(R.string.stores_dl_interrupted), fontSize = 12.sp, color = Color(0xFFFFC24D), maxLines = 1)
|
||||
Spacer(Modifier.weight(1f))
|
||||
RoundAction(Icons.Filled.PlayArrow, stringResource(R.string.stores_dl_resume), primary = true) { StoresState.requestInstall(ctx, item) }
|
||||
CancelAction { StoresState.clearUnfinished(ctx, item) }
|
||||
}
|
||||
@@ -204,20 +207,14 @@ private fun ArtStrip(cover: String?, name: String, content: @Composable androidx
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun androidx.compose.foundation.layout.RowScope.StripLabel(title: String, store: String, status: String, statusColor: Color) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.weight(1f)) {
|
||||
Text(title, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = Color.White, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f, fill = false))
|
||||
SourceChip(store, small = true)
|
||||
Text(status, fontSize = 12.sp, color = statusColor, maxLines = 1)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FinishedRow(d: DownloadEntry, s: FrontEndState, a: FrontEndActions, bloom: Boolean = false) {
|
||||
val ctx = LocalContext.current
|
||||
ArtStrip(d.cover, d.name) {
|
||||
StripLabel(d.name, d.store.id, stringResource(if (d.state == DownloadState.INSTALLED) R.string.stores_installed_chip else R.string.stores_dl_cancelled), Color.White.copy(alpha = 0.72f))
|
||||
Text(d.name, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = Color.White, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f, fill = false))
|
||||
SourceChip(d.store.id, small = true)
|
||||
Text(stringResource(if (d.state == DownloadState.INSTALLED) R.string.stores_installed_chip else R.string.stores_dl_cancelled), fontSize = 12.sp, color = Color.White.copy(alpha = 0.72f), maxLines = 1)
|
||||
Spacer(Modifier.weight(1f))
|
||||
if (d.state == DownloadState.INSTALLED) Box(Modifier.bloomIn(bloom, GATHER_HOLD_MS + 150)) {
|
||||
RoundAction(Icons.Filled.PlayArrow, stringResource(R.string.stores_play), primary = true) { if (d.key == EpicClient.DOWNLOAD_KEY) a.onEpicLauncher() else launchStoreGame(ctx, d.store, d.id, s, a) }
|
||||
}
|
||||
|
||||
@@ -90,7 +90,6 @@ import androidx.compose.material.icons.automirrored.filled.Login
|
||||
import androidx.compose.material.icons.filled.GridView
|
||||
import androidx.compose.material.icons.filled.Sort
|
||||
import androidx.compose.material.icons.filled.ViewList
|
||||
import androidx.compose.material.icons.filled.ViewModule
|
||||
import com.droiddeck.launcher.stores.sortCatalogItems
|
||||
import com.droiddeck.launcher.stores.retainCatalogOrder
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
@@ -149,9 +148,9 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
|
||||
private const val DOWNLOADS = "downloads"
|
||||
|
||||
private val TABS = listOf("library", "store")
|
||||
private val TABS = listOf("store", "library", "installed")
|
||||
|
||||
internal fun tabsFor(store: Store?): List<String> = if (store == Store.AMAZON) TABS.take(1) else TABS
|
||||
internal fun tabsFor(store: Store?): List<String> = if (store == Store.AMAZON) TABS.drop(1) else TABS
|
||||
|
||||
internal fun tabFor(store: Store?, tab: String): String = if (tab in tabsFor(store)) tab else "library"
|
||||
|
||||
@@ -215,13 +214,41 @@ internal fun StoresPage(s: FrontEndState, a: FrontEndActions, modifier: Modifier
|
||||
}
|
||||
}
|
||||
val pal = LocalPalette.current
|
||||
fun cycleStore(step: Int) {
|
||||
val ids = Store.entries.map { it.id }
|
||||
val at = ids.indexOf(chip)
|
||||
pickChip(ids[if (at < 0) (if (step > 0) 0 else ids.size - 1) else (at + step + ids.size) % ids.size])
|
||||
}
|
||||
fun openModes(st: Store) {
|
||||
if (chip != st.id) pickChip(st.id) else move("first")
|
||||
fun openModes(st: Store, at: Rect?) {
|
||||
if (chip != st.id) { pickChip(st.id); return }
|
||||
if (at == null || !StoresState.isSignedIn(st) || StoresState.expired[st] == true) { move("first"); return }
|
||||
val modes = tabsFor(st)
|
||||
val current = if (chip == st.id) shownTab else null
|
||||
var picked = false
|
||||
StoresMotion.ask(StepAsk(
|
||||
anchor = at, side = StepSide.Right, accent = storePalette(pal, st).signal, items = modes.size,
|
||||
onDismiss = { move(if (picked) "first" else "item:storechip:${st.id}") },
|
||||
handle = { ChipLabel(st, MaterialTheme.colorScheme.onBackground) },
|
||||
) {
|
||||
StoreTheme(st) {
|
||||
val library = StoresState.library[st].orEmpty()
|
||||
val installedCount = remember(StoresState.installed, library) { com.droiddeck.launcher.stores.installedCards(st, StoresState.installed, library).size }
|
||||
StepList {
|
||||
modes.forEachIndexed { i, m ->
|
||||
val label = when (m) {
|
||||
"store" -> stringResource(R.string.stores_tab_store)
|
||||
"installed" -> stringResource(R.string.stores_tab_count, stringResource(R.string.stores_tab_installed), installedCount)
|
||||
else -> stringResource(R.string.stores_tab_count, stringResource(R.string.stores_tab_library), library.size)
|
||||
}
|
||||
val focusHere = if (current != null) m == current else i == 0
|
||||
StepChoice(
|
||||
label, selected = m == current, enabled = open,
|
||||
modifier = Modifier.fillMaxWidth().stepItem(i).then(if (focusHere) Modifier.focusRequester(first) else Modifier),
|
||||
) {
|
||||
picked = true
|
||||
if (chip != st.id) { chip = st.id; openGame = null; query = "" }
|
||||
tab = m
|
||||
StoresMotion.fold()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
BackHandler(enabled = openGame != null) { closeGame() }
|
||||
val scroll = rememberScrollState()
|
||||
@@ -299,13 +326,13 @@ internal fun StoresPage(s: FrontEndState, a: FrontEndActions, modifier: Modifier
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = padH, vertical = if (narrow) 10.dp else 14.dp)
|
||||
.bumpers(
|
||||
onPrevious = { if (openGame == null) cycleStore(-1) },
|
||||
onNext = { if (openGame == null) cycleStore(1) },
|
||||
onPrevious = { if (openGame == null && chip != DOWNLOADS) { val t = tabsFor(store); switchTab(t[(t.indexOf(shownTab) + t.size - 1) % t.size]) } },
|
||||
onNext = { if (openGame == null && chip != DOWNLOADS) { val t = tabsFor(store); switchTab(t[(t.indexOf(shownTab) + 1) % t.size]) } },
|
||||
),
|
||||
) {
|
||||
Box(Modifier.fillMaxWidth().padding(bottom = 8.dp)) {
|
||||
StoreTheme(Store.byId(focusedChip ?: chip)) {
|
||||
Rise(0) { StoreChips(chip, focusedChip, s.storeDownloadsActive, onStore = { st, _ -> openModes(st) }, onDownloads = { if (chip != DOWNLOADS) pickChip(DOWNLOADS) else move("first") },
|
||||
Rise(0) { StoreChips(chip, focusedChip, s.storeDownloadsActive, onStore = { st, at -> openModes(st, at) }, onDownloads = { if (chip != DOWNLOADS) pickChip(DOWNLOADS) else move("first") },
|
||||
onFocusChip = { focusedChip = it }, onSettings = { at -> if (at != null) StoresMotion.ask(storesSettingsStep(at, liveState, a, pal.signal)) }) }
|
||||
}
|
||||
}
|
||||
@@ -363,7 +390,7 @@ internal fun StoresPage(s: FrontEndState, a: FrontEndActions, modifier: Modifier
|
||||
k.chip == DOWNLOADS -> Column(Modifier.fillMaxWidth().verticalScroll(scroll).padding(top = 4.dp, bottom = 16.dp)) { StoresDownloadsPane(s, a) }
|
||||
st == null -> {}
|
||||
!k.signedIn -> Box(Modifier.fillMaxWidth().verticalScroll(scroll).padding(top = 4.dp, bottom = 16.dp)) { SignInCard(st, viewportH - 20.dp) }
|
||||
else -> Storefront(st, k.tab, query, s, a, grids.getOrPut(k) { LazyGridState() }, onQuery = { query = it }, onOpen = { open(it) }, onTab = { switchTab(it) })
|
||||
else -> Storefront(st, k.tab, query, s, a, grids.getOrPut(k) { LazyGridState() }, onQuery = { query = it }, onOpen = { open(it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -549,14 +576,13 @@ private fun StoreChips(
|
||||
) { onStore(store, rootBounds[store.id]) }
|
||||
}
|
||||
StoreChip(
|
||||
label = null, on = selected == DOWNLOADS, under = fillShown && highlighted == DOWNLOADS, key = DOWNLOADS, mark = pal.signal,
|
||||
modifier = Modifier.onGloballyPositioned {
|
||||
label = stringResource(R.string.stores_tab_downloads), on = selected == DOWNLOADS, under = fillShown && highlighted == DOWNLOADS, key = DOWNLOADS, mark = pal.signal,
|
||||
modifier = Modifier.weight(1f).onGloballyPositioned {
|
||||
bounds[DOWNLOADS] = it.boundsInParent()
|
||||
StoresMotion.downloadsChip = it.boundsInRoot()
|
||||
},
|
||||
lead = { Icon(Icons.Outlined.Download, null, modifier = Modifier.size(18.dp)) },
|
||||
trail = if (active > 0) { { CountPill(active) } } else null,
|
||||
description = stringResource(R.string.stores_tab_downloads),
|
||||
onFocused = onFocusChip,
|
||||
) { onDownloads() }
|
||||
var cogAt by remember { mutableStateOf<Rect?>(null) }
|
||||
@@ -570,7 +596,7 @@ private fun StoreChips(
|
||||
|
||||
@Composable
|
||||
private fun StoreChip(
|
||||
label: String?, on: Boolean, under: Boolean, key: String, mark: Color, modifier: Modifier, lead: @Composable () -> Unit,
|
||||
label: String, on: Boolean, under: Boolean, key: String, mark: Color, modifier: Modifier, lead: @Composable () -> Unit,
|
||||
trail: (@Composable () -> Unit)? = null, description: String? = null,
|
||||
onFocused: (String?) -> Unit = {}, onClick: () -> Unit,
|
||||
) {
|
||||
@@ -608,7 +634,7 @@ private fun StoreChip(
|
||||
androidx.compose.runtime.CompositionLocalProvider(androidx.compose.material3.LocalContentColor provides ink) {
|
||||
Box(Modifier.pulseRing(picked, mark), contentAlignment = Alignment.Center) { lead() }
|
||||
}
|
||||
if (label != null) Text(label, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = ink, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(label, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = ink, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
trail?.invoke()
|
||||
}
|
||||
}
|
||||
@@ -698,16 +724,11 @@ private fun SignInButton(store: Store, large: Boolean = false) {
|
||||
val ctx = LocalContext.current
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val placed = remember { arrayOfNulls<androidx.compose.ui.layout.LayoutCoordinates>(1) }
|
||||
var askKey by remember { mutableStateOf(false) }
|
||||
if (askKey) ItchKeyDialog { askKey = false }
|
||||
Box(Modifier.onGloballyPositioned { placed[0] = it }) {
|
||||
PrimaryButton(stringResource(R.string.stores_signin), main = true, large = large, icon = if (large) Icons.AutoMirrored.Filled.Login else null) {
|
||||
if (store == Store.ITCH) askKey = true
|
||||
else {
|
||||
val at = placed[0]?.takeIf { it.isAttached }?.boundsInRoot()
|
||||
if (at == null || Motion.scale == 0f || StoresMotion.signIn != null) StoresState.signIn(ctx, store)
|
||||
else StoresMotion.signIn = SignInFlood(store, at, colors.primary, sourceColours(store.id).dot)
|
||||
}
|
||||
val at = placed[0]?.takeIf { it.isAttached }?.boundsInRoot()
|
||||
if (at == null || Motion.scale == 0f || StoresMotion.signIn != null) StoresState.signIn(ctx, store)
|
||||
else StoresMotion.signIn = SignInFlood(store, at, colors.primary, sourceColours(store.id).dot)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -725,22 +746,20 @@ internal fun StoreLogo(store: Store, size: Dp) {
|
||||
private fun Storefront(
|
||||
store: Store, tab: String, query: String, s: FrontEndState, a: FrontEndActions,
|
||||
grid: LazyGridState,
|
||||
onQuery: (String) -> Unit, onOpen: (String) -> Unit, onTab: (String) -> Unit,
|
||||
onQuery: (String) -> Unit, onOpen: (String) -> Unit,
|
||||
) {
|
||||
val ctx = LocalContext.current
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val library = StoresState.library[store] ?: emptyList()
|
||||
val shelves = StoresState.shelves[store]
|
||||
val found = StoresState.found[store] ?: emptyList()
|
||||
val installedItems = remember(StoresState.installed, library) { com.droiddeck.launcher.stores.installedCards(store, StoresState.installed, library) }
|
||||
val installedKeys = remember(installedItems) { installedItems.map { it.id }.toSet() + StoresState.installed.filter { it.sidecar.store == store }.map { it.sidecar.id } }
|
||||
val q = query.trim().lowercase()
|
||||
val foundKeys = remember(found) { found.map { it.key }.toSet() }
|
||||
fun matches(i: CatalogItem) = q.isEmpty() || i.title.lowercase().contains(q) || i.key in foundKeys
|
||||
fun matches(i: CatalogItem) = q.isEmpty() || i.title.lowercase().contains(q)
|
||||
fun shown(i: CatalogItem) = s.storesShowMature || !i.mature || i.owned
|
||||
fun shelf(l: List<CatalogItem>?) = l?.filter(::shown)
|
||||
val everything = remember(library, shelves, found) { (found + library + (shelves?.all ?: emptyList())).distinctBy { it.id } }
|
||||
LaunchedEffect(store, q) { StoresState.search(ctx, store, q) }
|
||||
val everything = remember(library, shelves) { (library + (shelves?.all ?: emptyList())).distinctBy { it.id } }
|
||||
val installedOnly = tab == "installed"
|
||||
val loaded = StoresState.library.containsKey(store)
|
||||
val showGrid = tab != "store" || q.isNotEmpty()
|
||||
val sort = s.storesSort
|
||||
@@ -754,45 +773,63 @@ private fun Storefront(
|
||||
val lookup: (CatalogItem) -> Long = { i -> byKey[i.key] ?: 0L }
|
||||
lookup
|
||||
}
|
||||
val isSizeSort = tab == "library" && (sort == "sizeAsc" || sort == "sizeDesc")
|
||||
val candidates = remember(library, everything, tab, q, s.storesShowMature) {
|
||||
if (tab == "library") library.filter(::matches) else everything.filter { matches(it) && shown(it) }
|
||||
val isSizeSort = (tab == "library" || tab == "installed") && (sort == "sizeAsc" || sort == "sizeDesc")
|
||||
val candidates = remember(library, installedItems, everything, tab, q, s.storesShowMature) {
|
||||
when (tab) {
|
||||
"library" -> library.filter(::matches)
|
||||
"installed" -> installedItems.filter(::matches)
|
||||
else -> everything.filter { matches(it) && shown(it) }
|
||||
}
|
||||
}
|
||||
val candidateKeys = remember(candidates) { candidates.map { it.key } }
|
||||
var resolvedSizeSortKey by remember(store, tab) { mutableStateOf<List<String>?>(null) }
|
||||
val sizeSortPending = isSizeSort && resolvedSizeSortKey != candidateKeys
|
||||
var stableVisible by remember(store, tab) { mutableStateOf(emptyList<CatalogItem>()) }
|
||||
val resolvedSizes = if (isSizeSort) com.droiddeck.launcher.stores.StoreSizes.known.toMap() else emptyMap()
|
||||
val sizeFor = remember(resolvedSizes) {
|
||||
val resolvedSizes = when {
|
||||
!isSizeSort -> emptyMap<String, Long>()
|
||||
installedOnly -> com.droiddeck.launcher.stores.StoreSizes.onDisk.toMap()
|
||||
else -> com.droiddeck.launcher.stores.StoreSizes.known.toMap()
|
||||
}
|
||||
val sizeFor = remember(resolvedSizes, installedOnly) {
|
||||
val resolved = resolvedSizes
|
||||
val lookup: (CatalogItem) -> Long = { item -> resolved[item.key] ?: item.sizeBytes }
|
||||
val lookup: (CatalogItem) -> Long = { item ->
|
||||
resolved[item.key] ?: if (installedOnly) 0L else item.sizeBytes
|
||||
}
|
||||
lookup
|
||||
}
|
||||
val sortedVisible = remember(candidates, tab, sort, lastPlayedFor, installedAtFor, sizeFor, installedKeys) {
|
||||
if (tab == "library") installedFirst(sortCatalogItems(candidates, sort, lastPlayed = lastPlayedFor, installedAt = installedAtFor, size = sizeFor), installedKeys)
|
||||
val sortedVisible = remember(candidates, tab, sort, lastPlayedFor, installedAtFor, sizeFor) {
|
||||
if (tab == "library" || tab == "installed")
|
||||
sortCatalogItems(candidates, sort, lastPlayed = lastPlayedFor, installedAt = installedAtFor, size = sizeFor)
|
||||
else candidates
|
||||
}
|
||||
val visible = remember(sizeSortPending, candidates, stableVisible, sortedVisible) {
|
||||
if (sizeSortPending) retainCatalogOrder(candidates, stableVisible) else sortedVisible
|
||||
}
|
||||
val installedShown = if (tab == "library") visible.count { it.id in installedKeys } else 0
|
||||
val split = installedShown.takeIf { it in 1 until visible.size } ?: 0
|
||||
SideEffect {
|
||||
if (!sizeSortPending && stableVisible != sortedVisible) stableVisible = sortedVisible
|
||||
}
|
||||
LaunchedEffect(store, tab, sort, candidates, candidateKeys) {
|
||||
if (!isSizeSort || resolvedSizeSortKey == candidateKeys) return@LaunchedEffect
|
||||
val requests = candidates.map { item ->
|
||||
item to com.droiddeck.launcher.stores.StoreSizes.resolve(ctx, item)
|
||||
val future: CompletableFuture<Long?> = if (tab == "installed") {
|
||||
StoresState.installedGame(store, item.id)?.let {
|
||||
com.droiddeck.launcher.stores.StoreSizes.resolveDisk(ctx, item.key, it)
|
||||
} ?: CompletableFuture.completedFuture<Long?>(null)
|
||||
} else {
|
||||
com.droiddeck.launcher.stores.StoreSizes.resolve(ctx, item)
|
||||
}
|
||||
item to future
|
||||
}
|
||||
CompletableFuture.allOf(*requests.map { it.second }.toTypedArray()).await()
|
||||
requests.forEach { (item, future) ->
|
||||
val bytes = future.getNow(null) ?: return@forEach
|
||||
if (item.owned && item.sizeBytes <= 0) com.droiddeck.launcher.stores.StoreSizes.known[item.key] = bytes
|
||||
if (tab == "installed") com.droiddeck.launcher.stores.StoreSizes.onDisk[item.key] = bytes
|
||||
else if (item.owned && item.sizeBytes <= 0) com.droiddeck.launcher.stores.StoreSizes.known[item.key] = bytes
|
||||
}
|
||||
resolvedSizeSortKey = candidateKeys
|
||||
}
|
||||
val empty = stringResource(when {
|
||||
installedOnly -> R.string.stores_installed_empty
|
||||
tab == "library" && library.isEmpty() && StoresState.status[store] != null -> R.string.stores_library_loading
|
||||
else -> R.string.stores_nothing_matches
|
||||
})
|
||||
@@ -803,9 +840,7 @@ private fun Storefront(
|
||||
val scope = rememberCoroutineScope()
|
||||
val headerItems = 1 + (if (sizeSortPending) 1 else 0) +
|
||||
(if (StoresState.status[store] != null) 1 else 0) + (if (StoresState.problems[store] != null) 1 else 0)
|
||||
val listMode = s.storesView == "list" && tab == "library"
|
||||
val capsules = s.storesView == "capsules" && tab == "library"
|
||||
val tileWidth = if (capsules) CapsuleWidth else CardWidth
|
||||
val listMode = s.storesView == "list" && (tab == "library" || tab == "installed")
|
||||
val card: @Composable (CatalogItem) -> Unit = { item ->
|
||||
val first = if (showGrid) item.key == visible.firstOrNull()?.key else !firstPlaced
|
||||
firstPlaced = true
|
||||
@@ -817,14 +852,14 @@ private fun Storefront(
|
||||
else -> 0
|
||||
}
|
||||
val index = visible.indexOfFirst { it.key == item.key }
|
||||
val next = storeGridNeighbor(index, visible.size, if (listMode) 1 else columns, direction, split)
|
||||
val next = storeGridNeighbor(index, visible.size, if (listMode) 1 else columns, direction)
|
||||
when {
|
||||
direction == 0 || index < 0 -> false
|
||||
next == null -> direction > 0
|
||||
else -> {
|
||||
if (key.action == KeyEvent.ACTION_DOWN) {
|
||||
val target = "card:${visible[next].key}"
|
||||
val targetIndex = headerItems + next + if (split == 0) 0 else if (next < split) 1 else 2
|
||||
val targetIndex = headerItems + next
|
||||
val visibleTarget = grid.layoutInfo.visibleItemsInfo.any { it.index == targetIndex }
|
||||
if (!visibleTarget || (focus.attached[target] ?: 0) == 0 ||
|
||||
runCatching { focus.items.getValue(target).requestFocus() }.isFailure) scope.launch {
|
||||
@@ -837,12 +872,12 @@ private fun Storefront(
|
||||
}
|
||||
}
|
||||
if (listMode) GameListRow(item, store, installedKeys, onOpen, first, navigation)
|
||||
else GameCard(item, store, s, a, installedKeys, onOpen, first, navigation, capsule = capsules)
|
||||
else GameCard(item, store, s, a, installedKeys, onOpen, first, navigation)
|
||||
}
|
||||
LazyVerticalGrid(
|
||||
columns = if (listMode) GridCells.Fixed(1) else GridCells.Adaptive(tileWidth), state = grid, modifier = Modifier.fillMaxSize().focusGroup().onSizeChanged {
|
||||
columns = if (listMode) GridCells.Fixed(1) else GridCells.Adaptive(CardWidth), state = grid, modifier = Modifier.fillMaxSize().focusGroup().onSizeChanged {
|
||||
val gap = with(density) { 10.dp.roundToPx() }
|
||||
val minimum = with(density) { (if (listMode) CardWidth * 2 else tileWidth).roundToPx() }
|
||||
val minimum = with(density) { (if (listMode) CardWidth * 2 else CardWidth).roundToPx() }
|
||||
columns = ((it.width + gap) / (minimum + gap)).toInt().coerceAtLeast(1)
|
||||
},
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp), verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
@@ -850,19 +885,21 @@ private fun Storefront(
|
||||
) {
|
||||
item(key = "header", span = { GridItemSpan(maxLineSpan) }) {
|
||||
Rise(1) {
|
||||
val tabs = tabsFor(store)
|
||||
val libraryLabel = if (loaded) stringResource(R.string.stores_tab_count, stringResource(R.string.stores_tab_library), library.size)
|
||||
else stringResource(R.string.stores_tab_library)
|
||||
ModeHeader(
|
||||
libraryLabel,
|
||||
when {
|
||||
tab == "store" -> stringResource(R.string.stores_tab_store)
|
||||
!loaded -> stringResource(if (installedOnly) R.string.stores_tab_installed else R.string.stores_tab_library)
|
||||
installedOnly -> stringResource(R.string.stores_tab_count, stringResource(R.string.stores_tab_installed), installedItems.size)
|
||||
else -> stringResource(R.string.stores_tab_count, stringResource(R.string.stores_tab_library), library.size)
|
||||
},
|
||||
query, onQuery,
|
||||
placeholder = if (tab == "store") stringResource(R.string.stores_search_catalog, store.label) else stringResource(R.string.stores_search_library),
|
||||
lead = if (tabs.size < 2) null else { {
|
||||
val labels = tabs.map { if (it == "store") stringResource(R.string.stores_tab_store) else libraryLabel }
|
||||
HeaderTabs(labels, tabs.indexOf(tab).coerceAtLeast(0)) { onTab(tabs[it]) }
|
||||
} },
|
||||
placeholder = when {
|
||||
tab == "store" -> stringResource(R.string.stores_search_catalog, store.label)
|
||||
installedOnly -> stringResource(R.string.stores_search_installed)
|
||||
else -> stringResource(R.string.stores_search_library)
|
||||
},
|
||||
) {
|
||||
if (tab == "library") {
|
||||
if (tab == "library" || tab == "installed") {
|
||||
SortButton(s, a)
|
||||
ViewButton(s, a)
|
||||
}
|
||||
@@ -888,14 +925,8 @@ private fun Storefront(
|
||||
StoresState.status[store]?.let { line -> item(key = "sync", span = { GridItemSpan(maxLineSpan) }) { Rise(3) { SyncBar(line) } } }
|
||||
StoresState.problems[store]?.let { line -> item(key = "problem", span = { GridItemSpan(maxLineSpan) }) { Rise(3) { Box(Modifier.padding(bottom = 8.dp)) { Note(line) } } } }
|
||||
when (tab) {
|
||||
"library" -> if (!loaded) Unit
|
||||
else if (split == 0) storeGrid(visible, empty, card)
|
||||
else {
|
||||
item(key = "installed-label", span = { GridItemSpan(maxLineSpan) }) { SectionLabel(stringResource(R.string.stores_section_installed)) }
|
||||
storeGrid(visible.take(split), empty, card)
|
||||
item(key = "not-installed-label", span = { GridItemSpan(maxLineSpan) }) { SectionLabel(stringResource(R.string.stores_section_not_installed)) }
|
||||
storeGrid(visible.drop(split), empty, card)
|
||||
}
|
||||
"library", "installed" -> if (!loaded) Unit
|
||||
else storeGrid(visible, empty, card)
|
||||
else -> {
|
||||
if (q.isNotEmpty()) storeGrid(visible, empty, card)
|
||||
else {
|
||||
@@ -925,48 +956,14 @@ private suspend fun <T> CompletableFuture<T>.await(): T =
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SectionLabel(title: String) {
|
||||
Text(
|
||||
title.uppercase(), fontSize = 12.sp, fontWeight = FontWeight.SemiBold, letterSpacing = 1.5.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.fillMaxWidth().padding(top = 6.dp, start = 2.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HeaderTabs(labels: List<String>, selected: Int, onSelect: (Int) -> Unit) {
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val pal = LocalPalette.current
|
||||
val shape = RoundedCornerShape(8.dp)
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
labels.forEachIndexed { i, label ->
|
||||
val src = remember { MutableInteractionSource() }
|
||||
val hot = rememberHot(src)
|
||||
val pick = { onSelect(i) }
|
||||
Text(
|
||||
label, fontSize = 15.sp, fontWeight = FontWeight.Bold, maxLines = 1,
|
||||
color = if (i == selected || hot) colors.onBackground else colors.onSurfaceVariant.copy(alpha = 0.7f),
|
||||
modifier = Modifier.clip(shape).glideBorder(hot, shape, pal.signal)
|
||||
.hoverable(src).clickable(interactionSource = src, indication = LocalIndication.current, role = Role.Tab, onClick = pick)
|
||||
.controllerConfirm(onClick = pick)
|
||||
.padding(horizontal = 4.dp, vertical = 6.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ModeHeader(
|
||||
title: String, query: String, onQuery: (String) -> Unit, placeholder: String,
|
||||
lead: (@Composable () -> Unit)? = null, trailing: @Composable () -> Unit,
|
||||
) {
|
||||
internal fun ModeHeader(title: String, query: String, onQuery: (String) -> Unit, placeholder: String, trailing: @Composable () -> Unit) {
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val pal = LocalPalette.current
|
||||
var searching by remember { mutableStateOf(query.isNotEmpty()) }
|
||||
val open by animateFloatAsState(if (searching) 1f else 0f, Motion.sp(0.75f, 420f), label = "searchOpen")
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.fillMaxWidth().heightIn(min = 52.dp).padding(vertical = 4.dp)) {
|
||||
if (lead != null) lead()
|
||||
else Text(
|
||||
Text(
|
||||
title, fontSize = 15.sp, fontWeight = FontWeight.Bold, color = colors.onBackground, maxLines = 1,
|
||||
modifier = Modifier.padding(start = 4.dp, end = 4.dp),
|
||||
)
|
||||
@@ -988,12 +985,19 @@ internal fun ModeHeader(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChipLabel(store: Store, ink: Color) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Box(Modifier.size(12.dp).clip(CircleShape).background(sourceColours(store.id).dot))
|
||||
Text(store.shortLabel, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = ink, maxLines = 1)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun storePalette(base: Palette, store: Store?): Palette {
|
||||
val (accent, deep) = when (store) {
|
||||
Store.GOG -> Color(0xFFC25BC8) to Color(0xFF9A3FA1)
|
||||
Store.EPIC -> Color(0xFFE6E6E6) to Color(0xFFBDBDBD)
|
||||
Store.AMAZON -> Color(0xFFFF9900) to Color(0xFFE07800)
|
||||
Store.ITCH -> Color(0xFFFF5C5C) to Color(0xFFD93B3B)
|
||||
null -> return base
|
||||
}
|
||||
val on = if (accent.luminance() > 0.18f) Color(0xFF0B0B0D) else Color.White
|
||||
@@ -1097,13 +1101,12 @@ private fun SortButton(s: FrontEndState, a: FrontEndActions) {
|
||||
|
||||
@Composable
|
||||
private fun ViewButton(s: FrontEndState, a: FrontEndActions) {
|
||||
val views = SessionPrefs.STORES_VIEWS
|
||||
val next = views[(views.indexOf(s.storesView) + 1) % views.size]
|
||||
val list = s.storesView == "list"
|
||||
HeaderIconButton(
|
||||
stringResource(when (next) { "capsules" -> R.string.stores_view_capsules; "list" -> R.string.stores_view_list; else -> R.string.stores_view_cards }),
|
||||
when (next) { "capsules" -> Icons.Filled.ViewModule; "list" -> Icons.Filled.ViewList; else -> Icons.Filled.GridView },
|
||||
if (list) stringResource(R.string.stores_view_cards) else stringResource(R.string.stores_view_list),
|
||||
if (list) Icons.Filled.GridView else Icons.Filled.ViewList,
|
||||
"view",
|
||||
) { a.onStoresView(next) }
|
||||
) { a.onStoresView(if (list) "cards" else "list") }
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -1215,11 +1218,6 @@ private fun Shelf(title: String, items: List<CatalogItem>?, card: @Composable (C
|
||||
}
|
||||
}
|
||||
|
||||
internal fun installedFirst(items: List<CatalogItem>, installed: Set<String>): List<CatalogItem> {
|
||||
val (on, off) = items.partition { it.id in installed }
|
||||
return on + off
|
||||
}
|
||||
|
||||
private fun LazyGridScope.storeGrid(games: List<CatalogItem>, empty: String, card: @Composable (CatalogItem) -> Unit) {
|
||||
if (games.isEmpty()) item(key = "empty", span = { GridItemSpan(maxLineSpan) }) {
|
||||
Box(Modifier.padding(vertical = 32.dp).fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
@@ -1230,13 +1228,9 @@ private fun LazyGridScope.storeGrid(games: List<CatalogItem>, empty: String, car
|
||||
}
|
||||
|
||||
private val CardWidth = 112.dp
|
||||
private val CapsuleWidth = 92.dp
|
||||
|
||||
@Composable
|
||||
private fun GameCard(
|
||||
item: CatalogItem, store: Store, s: FrontEndState, a: FrontEndActions, installedKeys: Set<String>, onOpen: (String) -> Unit,
|
||||
first: Boolean = false, navigation: Modifier = Modifier, capsule: Boolean = false,
|
||||
) {
|
||||
private fun GameCard(item: CatalogItem, store: Store, s: FrontEndState, a: FrontEndActions, installedKeys: Set<String>, onOpen: (String) -> Unit, first: Boolean = false, navigation: Modifier = Modifier) {
|
||||
val ctx = LocalContext.current
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val pal = LocalPalette.current
|
||||
@@ -1249,8 +1243,7 @@ private fun GameCard(
|
||||
val placed = remember { arrayOfNulls<androidx.compose.ui.layout.LayoutCoordinates>(1) }
|
||||
val corner = with(androidx.compose.ui.platform.LocalDensity.current) { 10.dp.toPx() }
|
||||
val open = {
|
||||
val art = if (capsule) item.tallImageUrl ?: item.imageUrl else item.imageUrl ?: item.tallImageUrl
|
||||
placed[0]?.takeIf { it.isAttached }?.let { StoresMotion.markCard(CardMark(item.key, it.boundsInRoot(), corner, art)) }
|
||||
placed[0]?.takeIf { it.isAttached }?.let { StoresMotion.markCard(CardMark(item.key, it.boundsInRoot(), corner, item.imageUrl ?: item.tallImageUrl)) }
|
||||
onOpen(item.key)
|
||||
}
|
||||
val screen = with(androidx.compose.ui.platform.LocalDensity.current) { androidx.compose.ui.platform.LocalConfiguration.current.screenHeightDp.dp.toPx() }
|
||||
@@ -1268,7 +1261,7 @@ private fun GameCard(
|
||||
.clip(RoundedCornerShape(10.dp)).glideBorder(hot, RoundedCornerShape(10.dp), pal.signal, pal.line)
|
||||
.hoverable(src).clickable(interactionSource = src, indication = LocalIndication.current, onClick = open).controllerConfirm(onClick = open),
|
||||
) {
|
||||
CardFace(item, download, hot, capsule) { CardMarker(item, installed, download, compact = capsule) }
|
||||
CardFace(item, download, hot) { CardMarker(item, installed, download) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1290,28 +1283,13 @@ private val CardStrip = 26.dp
|
||||
|
||||
@OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun CardFace(
|
||||
item: CatalogItem, download: com.droiddeck.launcher.stores.download.DownloadEntry?, hot: Boolean, capsule: Boolean = false,
|
||||
marker: @Composable () -> Unit,
|
||||
) {
|
||||
val url = if (capsule) item.tallImageUrl ?: item.imageUrl else item.imageUrl ?: item.tallImageUrl
|
||||
private fun CardFace(item: CatalogItem, download: com.droiddeck.launcher.stores.download.DownloadEntry?, hot: Boolean, marker: @Composable () -> Unit) {
|
||||
val url = item.imageUrl ?: item.tallImageUrl
|
||||
val blurs = android.os.Build.VERSION.SDK_INT >= 31
|
||||
var landscape by remember(url) { mutableStateOf(false) }
|
||||
BoxWithConstraints(Modifier.fillMaxWidth()) {
|
||||
val total = if (capsule) maxWidth * 7f / 5f + CardStrip else maxWidth * 9f / 16f + CardStrip
|
||||
val total = maxWidth * 9f / 16f + CardStrip
|
||||
Box(Modifier.fillMaxWidth().height(total).background(Color(0xFF101318)).then(if (url == null) Modifier.background(artBrush(hueOf(item.title))) else Modifier)) {
|
||||
if (url != null && capsule && landscape) {
|
||||
// Stores without portrait covers (itch) would lose most of the art to a portrait crop.
|
||||
AsyncImage(model = url, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.matchParentSize().blur(24.dp))
|
||||
Spacer(Modifier.matchParentSize().background(Color.Black.copy(alpha = if (blurs) 0.35f else 0.7f)))
|
||||
AsyncImage(model = url, contentDescription = null, contentScale = ContentScale.Fit, modifier = Modifier.matchParentSize().padding(bottom = CardStrip))
|
||||
} else if (url != null) AsyncImage(
|
||||
model = url, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.matchParentSize(),
|
||||
onSuccess = { r ->
|
||||
val art = r.result.drawable
|
||||
if (capsule && art.intrinsicWidth > 0 && art.intrinsicHeight > 0) landscape = art.intrinsicWidth > art.intrinsicHeight * 0.95f
|
||||
},
|
||||
)
|
||||
if (url != null) AsyncImage(model = url, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.matchParentSize())
|
||||
Box(Modifier.align(Alignment.BottomCenter).fillMaxWidth().height(CardStrip).clipToBounds()) {
|
||||
if (url != null) AsyncImage(
|
||||
model = url, contentDescription = null, contentScale = ContentScale.Crop,
|
||||
@@ -1339,7 +1317,7 @@ private fun CardFace(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CardMarker(item: CatalogItem, installed: Boolean, download: com.droiddeck.launcher.stores.download.DownloadEntry?, compact: Boolean = false) {
|
||||
private fun CardMarker(item: CatalogItem, installed: Boolean, download: com.droiddeck.launcher.stores.download.DownloadEntry?) {
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val pal = LocalPalette.current
|
||||
val small = 10.sp
|
||||
@@ -1347,13 +1325,12 @@ private fun CardMarker(item: CatalogItem, installed: Boolean, download: com.droi
|
||||
installed -> Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(3.dp)) {
|
||||
val ctx = LocalContext.current
|
||||
val game = StoresState.installedGame(item.store, item.id)
|
||||
if (game != null && !compact) androidx.compose.runtime.LaunchedEffect(item.key) { com.droiddeck.launcher.stores.StoreSizes.requestDisk(ctx, item.key, game) }
|
||||
if (!compact) com.droiddeck.launcher.stores.StoreSizes.onDisk[item.key]?.let { Text(formatBytes(it), fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.White, maxLines = 1) }
|
||||
if (game != null) androidx.compose.runtime.LaunchedEffect(item.key) { com.droiddeck.launcher.stores.StoreSizes.requestDisk(ctx, item.key, game) }
|
||||
com.droiddeck.launcher.stores.StoreSizes.onDisk[item.key]?.let { Text(formatBytes(it), fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.White, maxLines = 1) }
|
||||
Icon(Icons.Filled.Check, stringResource(R.string.stores_installed_chip), tint = pal.good, modifier = Modifier.size(11.dp))
|
||||
}
|
||||
download != null -> Text("${download.percent}%", fontSize = small, fontWeight = FontWeight.Bold, color = pal.signal, maxLines = 1)
|
||||
item.owned && StoresState.isUnfinished(item) -> Text(stringResource(R.string.stores_resume), fontSize = small, fontWeight = FontWeight.Bold, color = pal.signal, maxLines = 1)
|
||||
item.owned && compact -> Unit
|
||||
item.owned -> com.droiddeck.launcher.stores.StoreSizes.size(item).takeIf { it > 0 }?.let { Text(formatBytes(it), fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.White, maxLines = 1) }
|
||||
item.isFree -> Text(stringResource(R.string.stores_free), fontSize = small, fontWeight = FontWeight.Bold, color = pal.good, maxLines = 1)
|
||||
item.isDiscounted -> Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
|
||||
@@ -290,7 +290,7 @@ public final class WaylandCompositor {
|
||||
|
||||
public static native void nativeSendSceneInput(int type, int a, int b);
|
||||
|
||||
public static final int FG_ENGINE_LSFG = 0, FG_ENGINE_DDFG = 1;
|
||||
public static final int FG_ENGINE_LSFG = 0, FG_ENGINE_WINFG = 1;
|
||||
|
||||
public static native void nativeSetFrameGenEngine(int kind);
|
||||
|
||||
@@ -300,7 +300,7 @@ public final class WaylandCompositor {
|
||||
|
||||
public static native void nativeSetFrameGenTuning(float flowScale, float refreshHz);
|
||||
|
||||
public static native void nativeSetDdFgTuning(int model, int perfPreset);
|
||||
public static native void nativeSetWinFgTuning(int model, int perfPreset);
|
||||
|
||||
public static native int nativeFrameGenProblem();
|
||||
public static native String nativeFrameGenCapsReason();
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background_light" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground_light" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 370 B |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 235 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 511 B |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 898 B |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">توليد الإطارات</string>
|
||||
<string name="frame_gen_hint">اختر وضع توليد الإطارات</string>
|
||||
<string name="frame_gen_off">إيقاف</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG تكيّفي %1$d</string>
|
||||
<string name="lsfg_needs_steam">يتطلب Lossless Scaling على Steam</string>
|
||||
@@ -741,9 +741,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (افتراضي)</string>
|
||||
<string name="setup_theme">المظهر</string>
|
||||
<string name="setup_theme_hint">مظهر المشغّل</string>
|
||||
<string name="setup_launcher_icon">أيقونة التطبيق</string>
|
||||
<string name="setup_launcher_icon_dark">داكنة</string>
|
||||
<string name="setup_launcher_icon_light">فاتحة</string>
|
||||
<string name="setup_home">استخدام كشاشة رئيسية</string>
|
||||
<string name="setup_home_on">يمكن أن يكون DroidDeck تطبيق الشاشة الرئيسية</string>
|
||||
<string name="setup_home_off">لا يُعرَض DroidDeck أبدًا كتطبيق للشاشة الرئيسية</string>
|
||||
@@ -2010,6 +2007,7 @@
|
||||
<string name="stores_settings_eyebrow">الإعدادات</string>
|
||||
<string name="stores_chip_signed_out">(لم يتم تسجيل الدخول)</string>
|
||||
<string name="stores_tab_store">المتجر</string>
|
||||
<string name="stores_tab_installed">المثبَّتة</string>
|
||||
<string name="stores_tab_library">المكتبة</string>
|
||||
<string name="stores_tab_all">الكل</string>
|
||||
<string name="stores_tab_downloads">التنزيلات</string>
|
||||
@@ -2021,8 +2019,10 @@
|
||||
<string name="stores_signout_stay">البقاء مسجَّل الدخول</string>
|
||||
<string name="stores_signout_go">تسجيل الخروج</string>
|
||||
<string name="stores_search_catalog">البحث في كتالوج %1$s…</string>
|
||||
<string name="stores_search_installed">البحث في الألعاب المثبَّتة…</string>
|
||||
<string name="stores_search_library">البحث في مكتبتك…</string>
|
||||
<string name="stores_search_open">البحث</string>
|
||||
<string name="stores_installed_empty">لا شيء مثبَّت</string>
|
||||
<string name="stores_library_loading">جارٍ التحميل</string>
|
||||
<string name="stores_nothing_matches">لا توجد ألعاب</string>
|
||||
<string name="stores_shelves_loading">جارٍ تحميل المتجر…</string>
|
||||
@@ -2041,19 +2041,7 @@
|
||||
<string name="stores_view_on">عرض على %1$s</string>
|
||||
<string name="stores_buy_price">شراء · %1$s</string>
|
||||
<string name="stores_installed_chip">مثبَّتة</string>
|
||||
<string name="stores_section_installed">مثبَّتة</string>
|
||||
<string name="stores_section_not_installed">غير مثبَّت</string>
|
||||
<string name="stores_in_library">في المكتبة</string>
|
||||
<string name="stores_itch_key">مفتاح API</string>
|
||||
<string name="stores_itch_get_key">الحصول على مفتاح</string>
|
||||
<string name="stores_itch_key_rejected">تم رفض المفتاح</string>
|
||||
<string name="stores_itch_not_owned">ليس في مكتبة itch.io الخاصة بك</string>
|
||||
<string name="stores_itch_no_upload">لا يوجد تنزيل لنظام Windows لهذه اللعبة</string>
|
||||
<string name="stores_itch_no_exe">لم يتم العثور على لعبة Windows في التنزيل</string>
|
||||
<string name="stores_itch_extract_failed">تعذر استخراج التنزيل (رمز الخروج %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">لم يستجب itch.io</string>
|
||||
<string name="stores_itch_archive">أرشيفات %1$s غير مدعومة</string>
|
||||
<string name="stores_itch_download_failed">استجاب itch.io بـ HTTP %1$d</string>
|
||||
<string name="stores_free">مجاني</string>
|
||||
<string name="stores_resume">استئناف</string>
|
||||
<string name="stores_refresh">تحديث</string>
|
||||
@@ -2067,7 +2055,6 @@
|
||||
<string name="stores_sort_release_date">تاريخ الإصدار</string>
|
||||
<string name="stores_view_list">عرض القائمة</string>
|
||||
<string name="stores_view_cards">عرض البطاقات</string>
|
||||
<string name="stores_view_capsules">عرض الأغلفة</string>
|
||||
<string name="stores_show_mature">إظهار المحتوى للبالغين</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
@@ -2700,61 +2687,33 @@
|
||||
<string name="storage_free_up">تحرير مساحة</string>
|
||||
<string name="storage_play_anyway">اللعب على أي حال</string>
|
||||
<plurals name="storage_games_installed">
|
||||
<item quantity="zero">%d مثبَّتة</item>
|
||||
<item quantity="one">%d مثبَّتة</item>
|
||||
<item quantity="two">%d مثبَّتة</item>
|
||||
<item quantity="few">%d مثبَّتة</item>
|
||||
<item quantity="many">%d مثبَّتة</item>
|
||||
<item quantity="other">%d مثبَّتة</item>
|
||||
</plurals>
|
||||
<plurals name="storage_games_on_sd">
|
||||
<item quantity="zero">%d على بطاقة SD</item>
|
||||
<item quantity="one">%d على بطاقة SD</item>
|
||||
<item quantity="two">%d على بطاقة SD</item>
|
||||
<item quantity="few">%d على بطاقة SD</item>
|
||||
<item quantity="many">%d على بطاقة SD</item>
|
||||
<item quantity="other">%d على بطاقة SD</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tools_count">
|
||||
<item quantity="zero">%d أداة</item>
|
||||
<item quantity="one">%d أداة</item>
|
||||
<item quantity="two">%d أداة</item>
|
||||
<item quantity="few">%d أدوات</item>
|
||||
<item quantity="many">%d أداة</item>
|
||||
<item quantity="other">%d أداة</item>
|
||||
<item quantity="other">%d أدوات</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_detail">
|
||||
<item quantity="zero">مجلدات بادئة فارغة وذاكرة شيدرات من %d لعبة</item>
|
||||
<item quantity="one">مجلدات بادئة فارغة وذاكرة شيدرات من %d لعبة</item>
|
||||
<item quantity="two">مجلدات بادئة فارغة وذاكرة شيدرات من %d لعبة</item>
|
||||
<item quantity="few">مجلدات بادئة فارغة وذاكرة شيدرات من %d ألعاب</item>
|
||||
<item quantity="many">مجلدات بادئة فارغة وذاكرة شيدرات من %d لعبة</item>
|
||||
<item quantity="other">مجلدات بادئة فارغة وذاكرة شيدرات من %d لعبة</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_unsure_detail">
|
||||
<item quantity="zero">بادئات %d لعبة تحتوي على ملفات، منها حفظات محلية. انسخها أولًا إن كنت تحتاجها.</item>
|
||||
<item quantity="one">بادئة %d لعبة تحتوي على ملفات، منها حفظات محلية. انسخها أولًا إن كنت تحتاجها.</item>
|
||||
<item quantity="two">بادئات %d لعبة تحتوي على ملفات، منها حفظات محلية. انسخها أولًا إن كنت تحتاجها.</item>
|
||||
<item quantity="few">بادئات %d ألعاب تحتوي على ملفات، منها حفظات محلية. انسخها أولًا إن كنت تحتاجها.</item>
|
||||
<item quantity="many">بادئات %d لعبة تحتوي على ملفات، منها حفظات محلية. انسخها أولًا إن كنت تحتاجها.</item>
|
||||
<item quantity="other">بادئات %d لعبة تحتوي على ملفات، منها حفظات محلية. انسخها أولًا إن كنت تحتاجها.</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tag_games">
|
||||
<item quantity="zero">%d لعبة</item>
|
||||
<item quantity="one">%d لعبة</item>
|
||||
<item quantity="two">%d لعبة</item>
|
||||
<item quantity="few">%d ألعاب</item>
|
||||
<item quantity="many">%d لعبة</item>
|
||||
<item quantity="other">%d لعبة</item>
|
||||
</plurals>
|
||||
<string name="storage_tool_default_proton">Proton الافتراضي في DroidDeck</string>
|
||||
<string name="storage_tool_proton">خيار Proton 11 في DroidDeck</string>
|
||||
<plurals name="storage_tool_custom_games">
|
||||
<item quantity="zero">تستخدمه %d لعبة</item>
|
||||
<item quantity="one">تستخدمه %d لعبة</item>
|
||||
<item quantity="two">تستخدمه %d لعبة</item>
|
||||
<item quantity="few">تستخدمه %d ألعاب</item>
|
||||
<item quantity="many">تستخدمه %d لعبة</item>
|
||||
<item quantity="other">تستخدمه %d لعبة</item>
|
||||
</plurals>
|
||||
<string name="storage_legend_components">المكوّنات</string>
|
||||
@@ -2797,20 +2756,12 @@
|
||||
<string name="storage_desktop_prune_body">يحذف بيئات التشغيل التي لا يحتاجها أي تطبيق مثبَّت وينظّف مخزن كائنات Flatpak. تبقى التطبيقات المثبَّتة تعمل.</string>
|
||||
<string name="storage_flatpak_uninstall_body">يحذف التطبيق (%1$s) وبيئات التشغيل التي لا يستخدمها غيره. تبقى بياناته في مجلدك الرئيسي.</string>
|
||||
<plurals name="storage_components_count">
|
||||
<item quantity="zero">%d حزمة</item>
|
||||
<item quantity="one">%d حزمة</item>
|
||||
<item quantity="two">%d حزمة</item>
|
||||
<item quantity="few">%d حزم</item>
|
||||
<item quantity="many">%d حزمة</item>
|
||||
<item quantity="other">%d حزمة</item>
|
||||
<item quantity="other">%d حزم</item>
|
||||
</plurals>
|
||||
<plurals name="storage_desktop_apps">
|
||||
<item quantity="zero">%d تطبيق</item>
|
||||
<item quantity="one">%d تطبيق</item>
|
||||
<item quantity="two">%d تطبيق</item>
|
||||
<item quantity="few">%d تطبيقات</item>
|
||||
<item quantity="many">%d تطبيقًا</item>
|
||||
<item quantity="other">%d تطبيق</item>
|
||||
<item quantity="other">%d تطبيقات</item>
|
||||
</plurals>
|
||||
<string name="boot_movie">فيديو الإقلاع</string>
|
||||
<string name="boot_movie_video">الفيديو</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Generación de fotogramas</string>
|
||||
<string name="frame_gen_hint">Elige el modo de generación de fotogramas</string>
|
||||
<string name="frame_gen_off">Desactivada</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG adaptativo %1$d</string>
|
||||
<string name="lsfg_needs_steam">Requiere Lossless Scaling en Steam</string>
|
||||
@@ -752,9 +752,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (predeterminada)</string>
|
||||
<string name="setup_theme">Tema</string>
|
||||
<string name="setup_theme_hint">Apariencia del launcher</string>
|
||||
<string name="setup_launcher_icon">Ícono de la app</string>
|
||||
<string name="setup_launcher_icon_dark">Oscuro</string>
|
||||
<string name="setup_launcher_icon_light">Claro</string>
|
||||
<string name="setup_home">Usar como pantalla de inicio</string>
|
||||
<string name="setup_home_on">DroidDeck puede ser la app de inicio</string>
|
||||
<string name="setup_home_off">DroidDeck nunca se ofrece como app de inicio</string>
|
||||
@@ -1977,6 +1974,7 @@
|
||||
<string name="stores_settings_eyebrow">Ajustes</string>
|
||||
<string name="stores_chip_signed_out">(sin iniciar sesión)</string>
|
||||
<string name="stores_tab_store">Tienda</string>
|
||||
<string name="stores_tab_installed">Instalados</string>
|
||||
<string name="stores_tab_library">Biblioteca</string>
|
||||
<string name="stores_tab_all">Todos</string>
|
||||
<string name="stores_tab_downloads">Descargas</string>
|
||||
@@ -1988,8 +1986,10 @@
|
||||
<string name="stores_signout_stay">Mantener la sesión</string>
|
||||
<string name="stores_signout_go">Cerrar sesión</string>
|
||||
<string name="stores_search_catalog">Buscar en el catálogo de %1$s…</string>
|
||||
<string name="stores_search_installed">Buscar juegos instalados…</string>
|
||||
<string name="stores_search_library">Buscar en tu biblioteca…</string>
|
||||
<string name="stores_search_open">Buscar</string>
|
||||
<string name="stores_installed_empty">Nada instalado</string>
|
||||
<string name="stores_library_loading">Cargando</string>
|
||||
<string name="stores_nothing_matches">No hay juegos</string>
|
||||
<string name="stores_shelves_loading">Cargando la tienda…</string>
|
||||
@@ -2008,8 +2008,6 @@
|
||||
<string name="stores_view_on">Ver en %1$s</string>
|
||||
<string name="stores_buy_price">Comprar · %1$s</string>
|
||||
<string name="stores_installed_chip">Instalado</string>
|
||||
<string name="stores_section_installed">Instalado</string>
|
||||
<string name="stores_section_not_installed">No instalada</string>
|
||||
<string name="stores_in_library">En la biblioteca</string>
|
||||
<string name="stores_free">Gratis</string>
|
||||
<string name="stores_resume">Reanudar</string>
|
||||
@@ -2024,7 +2022,6 @@
|
||||
<string name="stores_sort_release_date">Fecha de lanzamiento</string>
|
||||
<string name="stores_view_list">Vista de lista</string>
|
||||
<string name="stores_view_cards">Vista de tarjetas</string>
|
||||
<string name="stores_view_capsules">Vista de portadas</string>
|
||||
<string name="stores_show_mature">Mostrar contenido para adultos</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
@@ -2568,39 +2565,32 @@
|
||||
<string name="storage_play_anyway">Jugar igualmente</string>
|
||||
<plurals name="storage_games_installed">
|
||||
<item quantity="one">%d instalado</item>
|
||||
<item quantity="many">%d instalados</item>
|
||||
<item quantity="other">%d instalados</item>
|
||||
</plurals>
|
||||
<plurals name="storage_games_on_sd">
|
||||
<item quantity="one">%d en la tarjeta SD</item>
|
||||
<item quantity="many">%d en la tarjeta SD</item>
|
||||
<item quantity="other">%d en la tarjeta SD</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tools_count">
|
||||
<item quantity="one">%d herramienta</item>
|
||||
<item quantity="many">%d herramientas</item>
|
||||
<item quantity="other">%d herramientas</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_detail">
|
||||
<item quantity="one">Carpetas de prefijo vacías y caché de shaders de %d juego</item>
|
||||
<item quantity="many">Carpetas de prefijo vacías y cachés de shaders de %d juegos</item>
|
||||
<item quantity="other">Carpetas de prefijo vacías y cachés de shaders de %d juegos</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_unsure_detail">
|
||||
<item quantity="one">El prefijo de %d juego aún contiene archivos, que pueden incluir partidas guardadas locales. Haz una copia antes si las quieres.</item>
|
||||
<item quantity="many">Los prefijos de %d juegos aún contienen archivos, que pueden incluir partidas guardadas locales. Haz una copia antes si las quieres.</item>
|
||||
<item quantity="other">Los prefijos de %d juegos aún contienen archivos, que pueden incluir partidas guardadas locales. Haz una copia antes si las quieres.</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tag_games">
|
||||
<item quantity="one">%d juego</item>
|
||||
<item quantity="many">%d juegos</item>
|
||||
<item quantity="other">%d juegos</item>
|
||||
</plurals>
|
||||
<string name="storage_tool_default_proton">Proton predeterminado de DroidDeck</string>
|
||||
<string name="storage_tool_proton">Opción Proton 11 de DroidDeck</string>
|
||||
<plurals name="storage_tool_custom_games">
|
||||
<item quantity="one">Usado por %d juego</item>
|
||||
<item quantity="many">Usado por %d juegos</item>
|
||||
<item quantity="other">Usado por %d juegos</item>
|
||||
</plurals>
|
||||
<string name="storage_legend_components">Componentes</string>
|
||||
@@ -2644,12 +2634,10 @@
|
||||
<string name="storage_flatpak_uninstall_body">Quita la app (%1$s) y cualquier runtime que no use nada más. Sus datos en tu carpeta personal se quedan.</string>
|
||||
<plurals name="storage_components_count">
|
||||
<item quantity="one">%d paquete</item>
|
||||
<item quantity="many">%d paquetes</item>
|
||||
<item quantity="other">%d paquetes</item>
|
||||
</plurals>
|
||||
<plurals name="storage_desktop_apps">
|
||||
<item quantity="one">%d app</item>
|
||||
<item quantity="many">%d apps</item>
|
||||
<item quantity="other">%d apps</item>
|
||||
</plurals>
|
||||
<string name="boot_movie">Video de arranque</string>
|
||||
@@ -2669,16 +2657,6 @@
|
||||
<string name="boot_movie_converting_started">Convirtiendo %1$s para Steam. Mantén DroidDeck abierto.</string>
|
||||
<string name="boot_movie_unsupported">Este dispositivo no puede decodificar ese video.</string>
|
||||
<string name="boot_movie_import_failed">No se pudo importar ese video</string>
|
||||
<string name="stores_itch_key">Clave de API</string>
|
||||
<string name="stores_itch_get_key">Obtener una clave</string>
|
||||
<string name="stores_itch_key_rejected">Clave rechazada</string>
|
||||
<string name="stores_itch_not_owned">No está en tu biblioteca de itch.io</string>
|
||||
<string name="stores_itch_no_upload">Este juego no tiene descarga para Windows</string>
|
||||
<string name="stores_itch_no_exe">No se encontró un juego de Windows en la descarga</string>
|
||||
<string name="stores_itch_extract_failed">No se pudo extraer la descarga (salida %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io no respondió</string>
|
||||
<string name="stores_itch_archive">Los archivos %1$s no son compatibles</string>
|
||||
<string name="stores_itch_download_failed">itch.io respondió HTTP %1$d</string>
|
||||
<string name="glasses_experimental">Cámaras Luma Ultra — Experimental</string>
|
||||
<string name="glasses_joycons">Seguimiento de posición de los Joy-Con</string>
|
||||
<string name="glasses_joycons_hint">Usa las cámaras de las gafas inteligentes para posicionar tus mandos Joy-Con emparejados. La rotación y las pulsaciones de los botones provienen de los mandos Joy-Con. La posición se estima cuando tus muñecas están ocultas. Reemplaza los mandos operados con las manos desnudas y pausa el seguimiento de la cámara del teléfono. Apunta los mandos Joy-Con hacia adelante al conectar o restablecer el centro de seguimiento.</string>
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
<string name="frame_gen_title">Генериране на кадри</string>
|
||||
<string name="frame_gen_hint">Изберете режима на генериране на кадри</string>
|
||||
<string name="frame_gen_off">Изключено</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Адаптивно %1$d</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptive %1$d</string>
|
||||
<string name="lsfg_needs_steam">Изисква Lossless Scaling в Steam</string>
|
||||
<string name="lsfg_needs_install">Инсталирайте Lossless Scaling в Steam или импортирайте Lossless.dll</string>
|
||||
<string name="lsfg_import">Импортиране на Lossless.dll…</string>
|
||||
@@ -115,7 +115,7 @@
|
||||
<string name="fex_extreme_detail">Изключва проверките за самопроменящ се код. Игри с JIT и DRM може да спрат да работят.</string>
|
||||
<string name="fex_extreme_tso_label">Екстремно + TSO</string>
|
||||
<string name="fex_extreme_tso_detail">Екстремни настройки с включен основен TSO.</string>
|
||||
<string name="fex_extreme_gn_label">Екстремно-gn</string>
|
||||
<string name="fex_extreme_gn_label">Extreme-gn</string>
|
||||
<string name="fex_extreme_gn_detail">Производителност с TSC и подсказки за метаданни. Пробвайте преди Екстремно.</string>
|
||||
<string name="fex_denuvo_label">Denuvo</string>
|
||||
<string name="fex_denuvo_detail">Производителност с пълни проверки на кода и скрит hypervisor бит.</string>
|
||||
@@ -299,7 +299,7 @@
|
||||
<string name="mode_decky_installed">Инсталирано · %1$s</string>
|
||||
<string name="mode_decky_update">Налична актуализация · %1$s</string>
|
||||
<string name="mode_decky_current">Актуално · %1$s</string>
|
||||
<string name="mode_decky_loader">Зареждач</string>
|
||||
<string name="mode_decky_loader">Loader</string>
|
||||
<string name="mode_decky_install_latest">Инсталиране на последната версия</string>
|
||||
<string name="mode_decky_on">Стартира заедно със Steam. Докато е включено, други приложения на това устройство може да могат да контролират Steam.</string>
|
||||
<string name="mode_decky_off">Изключено. Портът за отдалечено дебъгване на Steam остава затворен.</string>
|
||||
@@ -729,9 +729,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (по подразбиране)</string>
|
||||
<string name="setup_theme">Тема</string>
|
||||
<string name="setup_theme_hint">Облик на лаунчъра</string>
|
||||
<string name="setup_launcher_icon">Икона на приложението</string>
|
||||
<string name="setup_launcher_icon_dark">Тъмна</string>
|
||||
<string name="setup_launcher_icon_light">Светла</string>
|
||||
<string name="setup_home">Използване като начален екран</string>
|
||||
<string name="setup_home_on">DroidDeck може да бъде приложението за начален екран</string>
|
||||
<string name="setup_home_off">DroidDeck никога не се предлага като приложение за начален екран</string>
|
||||
@@ -1100,7 +1097,7 @@
|
||||
<string name="second_terminal_not_ready">Сесията на Linux все още не е готова за терминал.</string>
|
||||
<string name="rail_steam">Steam</string>
|
||||
<string name="rail_games">Игри</string>
|
||||
<string name="rail_desktop">Работен плот</string>
|
||||
<string name="rail_desktop">Desktop</string>
|
||||
<string name="rail_store">Магазин</string>
|
||||
<string name="rail_components">Компоненти</string>
|
||||
<string name="rail_apps">Приложения</string>
|
||||
@@ -1926,6 +1923,7 @@
|
||||
<string name="stores_settings_eyebrow">Настройки</string>
|
||||
<string name="stores_chip_signed_out">(не е вписан)</string>
|
||||
<string name="stores_tab_store">Магазин</string>
|
||||
<string name="stores_tab_installed">Инсталирани</string>
|
||||
<string name="stores_tab_library">Библиотека</string>
|
||||
<string name="stores_tab_all">Всички</string>
|
||||
<string name="stores_tab_downloads">Изтегляния</string>
|
||||
@@ -1937,8 +1935,10 @@
|
||||
<string name="stores_signout_stay">Остани вписан</string>
|
||||
<string name="stores_signout_go">Изход</string>
|
||||
<string name="stores_search_catalog">Търсене в каталога на %1$s…</string>
|
||||
<string name="stores_search_installed">Търсене в инсталираните игри…</string>
|
||||
<string name="stores_search_library">Търсене в твоята библиотека…</string>
|
||||
<string name="stores_search_open">Търсене</string>
|
||||
<string name="stores_installed_empty">Нищо инсталирано</string>
|
||||
<string name="stores_library_loading">Зареждане</string>
|
||||
<string name="stores_nothing_matches">Няма игри</string>
|
||||
<string name="stores_shelves_loading">Зареждане на магазина…</string>
|
||||
@@ -1957,19 +1957,7 @@
|
||||
<string name="stores_view_on">Виж в %1$s</string>
|
||||
<string name="stores_buy_price">Купи · %1$s</string>
|
||||
<string name="stores_installed_chip">Инсталирана</string>
|
||||
<string name="stores_section_installed">Инсталирана</string>
|
||||
<string name="stores_section_not_installed">Не е инсталирано</string>
|
||||
<string name="stores_in_library">В библиотеката</string>
|
||||
<string name="stores_itch_key">API ключ</string>
|
||||
<string name="stores_itch_get_key">Вземи ключ</string>
|
||||
<string name="stores_itch_key_rejected">Ключът е отказан</string>
|
||||
<string name="stores_itch_not_owned">Не е в библиотеката ви в itch.io</string>
|
||||
<string name="stores_itch_no_upload">Няма изтегляне за Windows за тази игра</string>
|
||||
<string name="stores_itch_no_exe">В изтеглянето не е намерена игра за Windows</string>
|
||||
<string name="stores_itch_extract_failed">Изтеглянето не можа да бъде разархивирано (код %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io не отговори</string>
|
||||
<string name="stores_itch_archive">Архивите %1$s не се поддържат</string>
|
||||
<string name="stores_itch_download_failed">itch.io отговори HTTP %1$d</string>
|
||||
<string name="stores_free">Безплатна</string>
|
||||
<string name="stores_resume">Продължи</string>
|
||||
<string name="stores_refresh">Обнови</string>
|
||||
@@ -1983,7 +1971,6 @@
|
||||
<string name="stores_sort_release_date">Дата на издаване</string>
|
||||
<string name="stores_view_list">Изглед на списък</string>
|
||||
<string name="stores_view_cards">Изглед с карти</string>
|
||||
<string name="stores_view_capsules">Изглед с обложки</string>
|
||||
<string name="stores_show_mature">Показвай съдържание за възрастни</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
<string name="frame_gen_title">Generování snímků</string>
|
||||
<string name="frame_gen_hint">Vyberte režim generování snímků</string>
|
||||
<string name="frame_gen_off">Vypnuto</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptivní %1$d</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptive %1$d</string>
|
||||
<string name="lsfg_needs_steam">Vyžaduje Lossless Scaling na Steamu</string>
|
||||
<string name="lsfg_needs_install">Nainstalujte Lossless Scaling ve Steamu nebo naimportujte Lossless.dll</string>
|
||||
<string name="lsfg_import">Importovat Lossless.dll…</string>
|
||||
@@ -735,9 +735,6 @@
|
||||
<string name="setup_app_scale_default">%1$d %% (výchozí)</string>
|
||||
<string name="setup_theme">Téma</string>
|
||||
<string name="setup_theme_hint">Vzhled spouštěče</string>
|
||||
<string name="setup_launcher_icon">Ikona aplikace</string>
|
||||
<string name="setup_launcher_icon_dark">Tmavá</string>
|
||||
<string name="setup_launcher_icon_light">Světlá</string>
|
||||
<string name="setup_home">Použít jako domovskou obrazovku</string>
|
||||
<string name="setup_home_on">DroidDeck může být domovská aplikace</string>
|
||||
<string name="setup_home_off">DroidDeck se nikdy nenabízí jako domovská aplikace</string>
|
||||
@@ -1968,6 +1965,7 @@
|
||||
<string name="stores_settings_eyebrow">Nastavení</string>
|
||||
<string name="stores_chip_signed_out">(nepřihlášen)</string>
|
||||
<string name="stores_tab_store">Obchod</string>
|
||||
<string name="stores_tab_installed">Nainstalované</string>
|
||||
<string name="stores_tab_library">Knihovna</string>
|
||||
<string name="stores_tab_all">Vše</string>
|
||||
<string name="stores_tab_downloads">Stahování</string>
|
||||
@@ -1979,8 +1977,10 @@
|
||||
<string name="stores_signout_stay">Zůstat přihlášen</string>
|
||||
<string name="stores_signout_go">Odhlásit se</string>
|
||||
<string name="stores_search_catalog">Hledat v katalogu %1$s…</string>
|
||||
<string name="stores_search_installed">Hledat v nainstalovaných hrách…</string>
|
||||
<string name="stores_search_library">Hledat ve vaší knihovně…</string>
|
||||
<string name="stores_search_open">Hledat</string>
|
||||
<string name="stores_installed_empty">Nic není nainstalováno</string>
|
||||
<string name="stores_library_loading">Načítání</string>
|
||||
<string name="stores_nothing_matches">Žádné hry</string>
|
||||
<string name="stores_shelves_loading">Načítání obchodu…</string>
|
||||
@@ -1999,19 +1999,7 @@
|
||||
<string name="stores_view_on">Zobrazit na %1$s</string>
|
||||
<string name="stores_buy_price">Koupit · %1$s</string>
|
||||
<string name="stores_installed_chip">Nainstalováno</string>
|
||||
<string name="stores_section_installed">Nainstalováno</string>
|
||||
<string name="stores_section_not_installed">Není nainstalováno</string>
|
||||
<string name="stores_in_library">V knihovně</string>
|
||||
<string name="stores_itch_key">Klíč API</string>
|
||||
<string name="stores_itch_get_key">Získat klíč</string>
|
||||
<string name="stores_itch_key_rejected">Klíč odmítnut</string>
|
||||
<string name="stores_itch_not_owned">Není ve vaší knihovně itch.io</string>
|
||||
<string name="stores_itch_no_upload">Pro tuto hru není ke stažení verze pro Windows</string>
|
||||
<string name="stores_itch_no_exe">Ve stahování nebyla nalezena hra pro Windows</string>
|
||||
<string name="stores_itch_extract_failed">Stažený soubor nelze rozbalit (kód %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io neodpovědělo</string>
|
||||
<string name="stores_itch_archive">Archivy %1$s nejsou podporovány</string>
|
||||
<string name="stores_itch_download_failed">itch.io odpovědělo HTTP %1$d</string>
|
||||
<string name="stores_free">Zdarma</string>
|
||||
<string name="stores_resume">Pokračovat</string>
|
||||
<string name="stores_refresh">Obnovit</string>
|
||||
@@ -2025,7 +2013,6 @@
|
||||
<string name="stores_sort_release_date">Datum vydání</string>
|
||||
<string name="stores_view_list">Zobrazení seznamu</string>
|
||||
<string name="stores_view_cards">Zobrazení karet</string>
|
||||
<string name="stores_view_capsules">Zobrazení obalů</string>
|
||||
<string name="stores_show_mature">Zobrazovat obsah pro dospělé</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Billedgenerering</string>
|
||||
<string name="frame_gen_hint">Vælg metode til billedgenerering</string>
|
||||
<string name="frame_gen_off">Fra</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptiv %1$d</string>
|
||||
<string name="lsfg_needs_steam">Kræver Lossless Scaling på Steam</string>
|
||||
@@ -527,7 +527,7 @@
|
||||
<string name="perf_client_ui">Klientgrænseflade</string>
|
||||
<string name="perf_glthread">Threaded GL</string>
|
||||
<string name="perf_glthread_hint">Kan gøre Steam-menuer mere lydhøre.</string>
|
||||
<string name="perf_zink">Zink: dovne deskriptorer</string>
|
||||
<string name="perf_zink">Zink: lazy descriptors</string>
|
||||
<string name="perf_zink_hint">Anbefalet til drivere uden descriptor buffers.</string>
|
||||
<string name="perf_noglerror">Spring GL-fejlkontrol over</string>
|
||||
<string name="perf_noglerror_hint">Slår validering af GL fra for hvert kald.</string>
|
||||
@@ -549,7 +549,7 @@
|
||||
<string name="perf_xalia_hint">Deaktiverer Protons gamepad-navigationshjælper, som koster CPU i hvert spil.</string>
|
||||
<string name="perf_seccomp">Kør proot uden seccomp</string>
|
||||
<string name="perf_seccomp_hint">Kan afhjælpe fejl om manglende systemkald på nogle kerner. Kan reducere ydelsen.</string>
|
||||
<string name="perf_fastpath">proot-hurtigvej</string>
|
||||
<string name="perf_fastpath">proot fast path</string>
|
||||
<string name="perf_fastpath_needs_seccomp">Kræver prootets seccomp-filter.</string>
|
||||
<string name="perf_fastpath_hint">Hurtigere filopslag og mindre hak. Slå fra, hvis et program ikke kan finde sine filer.</string>
|
||||
<string name="perf_identity">Sessionsidentitet</string>
|
||||
@@ -729,9 +729,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (standard)</string>
|
||||
<string name="setup_theme">Tema</string>
|
||||
<string name="setup_theme_hint">Launcherens udseende</string>
|
||||
<string name="setup_launcher_icon">Appikon</string>
|
||||
<string name="setup_launcher_icon_dark">Mørk</string>
|
||||
<string name="setup_launcher_icon_light">Lys</string>
|
||||
<string name="setup_home">Brug som startskærm</string>
|
||||
<string name="setup_home_on">DroidDeck kan være Home-appen</string>
|
||||
<string name="setup_home_off">DroidDeck tilbydes aldrig som Home-app</string>
|
||||
@@ -945,7 +942,7 @@
|
||||
<string name="ctrl_size_hint" formatted="false">100% er standardstørrelsen</string>
|
||||
<string name="ctrl_stick_click">Stick-klik</string>
|
||||
<string name="ctrl_stick_click_hint">Dobbelttryk og hold en stick nede for L3 eller R3</string>
|
||||
<string name="ctrl_adaptive">Adaptive styrepinde</string>
|
||||
<string name="ctrl_adaptive">Adaptive sticks</string>
|
||||
<string name="ctrl_rumble">Spilvibration</string>
|
||||
<string name="ctrl_steam_button">Steam-knap</string>
|
||||
<string name="ctrl_qam_button">QAM-knap</string>
|
||||
@@ -1926,6 +1923,7 @@
|
||||
<string name="stores_settings_eyebrow">Indstillinger</string>
|
||||
<string name="stores_chip_signed_out">(ikke logget ind)</string>
|
||||
<string name="stores_tab_store">Butik</string>
|
||||
<string name="stores_tab_installed">Installeret</string>
|
||||
<string name="stores_tab_library">Bibliotek</string>
|
||||
<string name="stores_tab_all">Alle</string>
|
||||
<string name="stores_tab_downloads">Downloads</string>
|
||||
@@ -1937,8 +1935,10 @@
|
||||
<string name="stores_signout_stay">Bliv logget ind</string>
|
||||
<string name="stores_signout_go">Log ud</string>
|
||||
<string name="stores_search_catalog">Søg i %1$s-kataloget…</string>
|
||||
<string name="stores_search_installed">Søg i installerede spil…</string>
|
||||
<string name="stores_search_library">Søg i dit bibliotek…</string>
|
||||
<string name="stores_search_open">Søg</string>
|
||||
<string name="stores_installed_empty">Intet installeret</string>
|
||||
<string name="stores_library_loading">Indlæser</string>
|
||||
<string name="stores_nothing_matches">Ingen spil</string>
|
||||
<string name="stores_shelves_loading">Indlæser butikken…</string>
|
||||
@@ -1957,19 +1957,7 @@
|
||||
<string name="stores_view_on">Se på %1$s</string>
|
||||
<string name="stores_buy_price">Køb · %1$s</string>
|
||||
<string name="stores_installed_chip">Installeret</string>
|
||||
<string name="stores_section_installed">Installeret</string>
|
||||
<string name="stores_section_not_installed">Ikke installeret</string>
|
||||
<string name="stores_in_library">I biblioteket</string>
|
||||
<string name="stores_itch_key">API-nøgle</string>
|
||||
<string name="stores_itch_get_key">Hent en nøgle</string>
|
||||
<string name="stores_itch_key_rejected">Nøgle afvist</string>
|
||||
<string name="stores_itch_not_owned">Ikke i dit itch.io-bibliotek</string>
|
||||
<string name="stores_itch_no_upload">Ingen Windows-download til dette spil</string>
|
||||
<string name="stores_itch_no_exe">Der blev ikke fundet et Windows-spil i downloaden</string>
|
||||
<string name="stores_itch_extract_failed">Downloaden kunne ikke udpakkes (kode %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io svarede ikke</string>
|
||||
<string name="stores_itch_archive">%1$s-arkiver understøttes ikke</string>
|
||||
<string name="stores_itch_download_failed">itch.io svarede HTTP %1$d</string>
|
||||
<string name="stores_free">Gratis</string>
|
||||
<string name="stores_resume">Genoptag</string>
|
||||
<string name="stores_refresh">Opdater</string>
|
||||
@@ -1983,7 +1971,6 @@
|
||||
<string name="stores_sort_release_date">Udgivelsesdato</string>
|
||||
<string name="stores_view_list">Listevisning</string>
|
||||
<string name="stores_view_cards">Kortvisning</string>
|
||||
<string name="stores_view_capsules">Omslagsvisning</string>
|
||||
<string name="stores_show_mature">Vis indhold for voksne</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Frame-Generierung</string>
|
||||
<string name="frame_gen_hint">Modus der Frame-Generierung auswählen</string>
|
||||
<string name="frame_gen_off">Aus</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptiv %1$d</string>
|
||||
<string name="lsfg_needs_steam">Benötigt Lossless Scaling auf Steam</string>
|
||||
@@ -718,9 +718,6 @@
|
||||
<string name="setup_app_scale_default">%1$d %% (Standard)</string>
|
||||
<string name="setup_theme">Design</string>
|
||||
<string name="setup_theme_hint">Aussehen des Launchers</string>
|
||||
<string name="setup_launcher_icon">App-Symbol</string>
|
||||
<string name="setup_launcher_icon_dark">Dunkel</string>
|
||||
<string name="setup_launcher_icon_light">Hell</string>
|
||||
<string name="setup_home">Als Startbildschirm verwenden</string>
|
||||
<string name="setup_home_on">DroidDeck kann die Startbildschirm-App sein</string>
|
||||
<string name="setup_home_off">DroidDeck wird nie als Startbildschirm-App angeboten</string>
|
||||
@@ -1915,6 +1912,7 @@
|
||||
<string name="stores_settings_eyebrow">Einstellungen</string>
|
||||
<string name="stores_chip_signed_out">(nicht angemeldet)</string>
|
||||
<string name="stores_tab_store">Store</string>
|
||||
<string name="stores_tab_installed">Installiert</string>
|
||||
<string name="stores_tab_library">Bibliothek</string>
|
||||
<string name="stores_tab_all">Alle</string>
|
||||
<string name="stores_tab_downloads">Downloads</string>
|
||||
@@ -1926,8 +1924,10 @@
|
||||
<string name="stores_signout_stay">Angemeldet bleiben</string>
|
||||
<string name="stores_signout_go">Abmelden</string>
|
||||
<string name="stores_search_catalog">Katalog von %1$s durchsuchen…</string>
|
||||
<string name="stores_search_installed">Installierte Spiele durchsuchen…</string>
|
||||
<string name="stores_search_library">Deine Bibliothek durchsuchen…</string>
|
||||
<string name="stores_search_open">Suchen</string>
|
||||
<string name="stores_installed_empty">Nichts installiert</string>
|
||||
<string name="stores_library_loading">Wird geladen</string>
|
||||
<string name="stores_nothing_matches">Keine Spiele</string>
|
||||
<string name="stores_shelves_loading">Store wird geladen…</string>
|
||||
@@ -1946,19 +1946,7 @@
|
||||
<string name="stores_view_on">Auf %1$s ansehen</string>
|
||||
<string name="stores_buy_price">Kaufen · %1$s</string>
|
||||
<string name="stores_installed_chip">Installiert</string>
|
||||
<string name="stores_section_installed">Installiert</string>
|
||||
<string name="stores_section_not_installed">Nicht installiert</string>
|
||||
<string name="stores_in_library">In der Bibliothek</string>
|
||||
<string name="stores_itch_key">API-Schlüssel</string>
|
||||
<string name="stores_itch_get_key">Schlüssel holen</string>
|
||||
<string name="stores_itch_key_rejected">Schlüssel abgelehnt</string>
|
||||
<string name="stores_itch_not_owned">Nicht in deiner itch.io-Bibliothek</string>
|
||||
<string name="stores_itch_no_upload">Kein Windows-Download für dieses Spiel</string>
|
||||
<string name="stores_itch_no_exe">Im Download wurde kein Windows-Spiel gefunden</string>
|
||||
<string name="stores_itch_extract_failed">Download konnte nicht entpackt werden (Code %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io hat nicht geantwortet</string>
|
||||
<string name="stores_itch_archive">%1$s-Archive werden nicht unterstützt</string>
|
||||
<string name="stores_itch_download_failed">itch.io antwortete mit HTTP %1$d</string>
|
||||
<string name="stores_free">Kostenlos</string>
|
||||
<string name="stores_resume">Fortsetzen</string>
|
||||
<string name="stores_refresh">Aktualisieren</string>
|
||||
@@ -1972,7 +1960,6 @@
|
||||
<string name="stores_sort_release_date">Erscheinungsdatum</string>
|
||||
<string name="stores_view_list">Listenansicht</string>
|
||||
<string name="stores_view_cards">Kartenansicht</string>
|
||||
<string name="stores_view_capsules">Coveransicht</string>
|
||||
<string name="stores_show_mature">Inhalte für Erwachsene anzeigen</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Δημιουργία καρέ</string>
|
||||
<string name="frame_gen_hint">Επιλέξτε τη λειτουργία δημιουργίας καρέ</string>
|
||||
<string name="frame_gen_off">Ανενεργό</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG προσαρμοστικό %1$d</string>
|
||||
<string name="lsfg_needs_steam">Απαιτεί το Lossless Scaling στο Steam</string>
|
||||
@@ -115,7 +115,7 @@
|
||||
<string name="fex_extreme_detail">Απενεργοποιεί τους ελέγχους αυτο-τροποποιούμενου κώδικα. Παιχνίδια με JIT και DRM μπορεί να πάθουν βλάβη.</string>
|
||||
<string name="fex_extreme_tso_label">Ακραίο + TSO</string>
|
||||
<string name="fex_extreme_tso_detail">Ρυθμίσεις Ακραίο με το κύριο TSO ενεργοποιημένο.</string>
|
||||
<string name="fex_extreme_gn_label">Ακραίο-gn</string>
|
||||
<string name="fex_extreme_gn_label">Extreme-gn</string>
|
||||
<string name="fex_extreme_gn_detail">Απόδοση με υποδείξεις TSC και μεταδεδομένων. Δοκιμάστε το πριν το Ακραίο.</string>
|
||||
<string name="fex_denuvo_label">Denuvo</string>
|
||||
<string name="fex_denuvo_detail">Απόδοση με πλήρεις ελέγχους κώδικα και το bit υπερεπόπτη κρυμμένο.</string>
|
||||
@@ -299,7 +299,7 @@
|
||||
<string name="mode_decky_installed">Εγκατεστημένο · %1$s</string>
|
||||
<string name="mode_decky_update">Διαθέσιμη ενημέρωση · %1$s</string>
|
||||
<string name="mode_decky_current">Ενημερωμένο · %1$s</string>
|
||||
<string name="mode_decky_loader">Φορτωτής</string>
|
||||
<string name="mode_decky_loader">Loader</string>
|
||||
<string name="mode_decky_install_latest">Εγκατάσταση τελευταίας έκδοσης</string>
|
||||
<string name="mode_decky_on">Ξεκινά με το Steam. Όσο είναι ενεργό, άλλες εφαρμογές σε αυτή τη συσκευή μπορεί να είναι σε θέση να ελέγξουν το Steam.</string>
|
||||
<string name="mode_decky_off">Ανενεργό. Η θύρα απομακρυσμένου εντοπισμού σφαλμάτων του Steam παραμένει κλειστή.</string>
|
||||
@@ -527,7 +527,7 @@
|
||||
<string name="perf_client_ui">Διεπαφή πελάτη</string>
|
||||
<string name="perf_glthread">Νηματοποιημένο GL</string>
|
||||
<string name="perf_glthread_hint">Μπορεί να κάνει τα μενού του Steam πιο αποκρίσιμα.</string>
|
||||
<string name="perf_zink">Zink: καθυστερημένοι περιγραφείς</string>
|
||||
<string name="perf_zink">Zink: lazy descriptors</string>
|
||||
<string name="perf_zink_hint">Προτείνεται για προγράμματα οδήγησης χωρίς descriptor buffers.</string>
|
||||
<string name="perf_noglerror">Παράβλεψη ελέγχων σφαλμάτων GL</string>
|
||||
<string name="perf_noglerror_hint">Απενεργοποιεί την επικύρωση GL ανά κλήση.</string>
|
||||
@@ -575,7 +575,7 @@
|
||||
<string name="drawer_steam">Steam</string>
|
||||
<string name="drawer_desktop">Επιφάνεια εργασίας</string>
|
||||
<string name="drawer_steam_menu">Μενού Steam</string>
|
||||
<string name="drawer_quick_access">Γρήγορη πρόσβαση</string>
|
||||
<string name="drawer_quick_access">Quick access</string>
|
||||
<string name="drawer_open_qam">Άνοιγμα Quick Access Menu</string>
|
||||
<string name="drawer_prev_page">Προηγούμενη σελίδα</string>
|
||||
<string name="drawer_next_page">Επόμενη σελίδα</string>
|
||||
@@ -729,9 +729,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (Προεπιλογή)</string>
|
||||
<string name="setup_theme">Θέμα</string>
|
||||
<string name="setup_theme_hint">Εμφάνιση εκκινητή</string>
|
||||
<string name="setup_launcher_icon">Εικονίδιο εφαρμογής</string>
|
||||
<string name="setup_launcher_icon_dark">Σκούρο</string>
|
||||
<string name="setup_launcher_icon_light">Ανοιχτό</string>
|
||||
<string name="setup_home">Χρήση ως αρχική οθόνη</string>
|
||||
<string name="setup_home_on">Το DroidDeck μπορεί να είναι η εφαρμογή αρχικής οθόνης</string>
|
||||
<string name="setup_home_off">Το DroidDeck δεν προτείνεται ποτέ ως εφαρμογή αρχικής οθόνης</string>
|
||||
@@ -1062,7 +1059,7 @@
|
||||
<string name="load_runtime_detail">Προετοιμασία του runtime Linux</string>
|
||||
<string name="load_session">Συνεδρία</string>
|
||||
<string name="load_session_detail">Εκκίνηση οθόνης και ήχου</string>
|
||||
<string name="load_steam">Πρόγραμμα-πελάτης Steam</string>
|
||||
<string name="load_steam">Steam client</string>
|
||||
<string name="load_steam_detail">Έλεγχος του Steam και των εργαλείων συμβατότητάς του</string>
|
||||
<string name="load_opening_steam">Άνοιγμα Steam</string>
|
||||
<string name="load_opening_steam_detail">Αναμονή για το πρώτο καρέ του Steam</string>
|
||||
@@ -1431,8 +1428,8 @@
|
||||
<string name="fm_pin_current">Καρφίτσωμα τρέχοντος φακέλου</string>
|
||||
<string name="fm_no_favorites">Δεν υπάρχουν ακόμα αγαπημένα - καρφιτσώστε έναν φάκελο με το μενού ⋮ του για να έρχεστε εδώ γρήγορα.</string>
|
||||
<string name="fm_unpin_favorite">Κατάργηση από τα αγαπημένα</string>
|
||||
<string name="fm_size_zero">0 μπάιτ</string>
|
||||
<string name="fm_unit_bytes">μπάιτ</string>
|
||||
<string name="fm_size_zero">0 bytes</string>
|
||||
<string name="fm_unit_bytes">bytes</string>
|
||||
<string name="fm_prop_location">Τοποθεσία</string>
|
||||
<string name="fm_prop_type">Τύπος</string>
|
||||
<string name="fm_prop_size">Μέγεθος</string>
|
||||
@@ -1788,7 +1785,7 @@
|
||||
<string name="look_adaptive_sharpen_desc">Μόνο όξυνση, το χρώμα παραμένει ανεπηρέαστο.</string>
|
||||
<string name="look_filmic">Φιλμικό</string>
|
||||
<string name="look_filmic_desc">Καμπύλη σε στιλ φιλμ, ελαφρώς απαλή.</string>
|
||||
<string name="look_arcade">Αρκέιντ</string>
|
||||
<string name="look_arcade">Arcade</string>
|
||||
<string name="look_arcade_desc">Έντονο και δυναμικό.</string>
|
||||
<string name="look_retro_crt">Ρετρό CRT</string>
|
||||
<string name="look_retro_crt_desc">Ο σωλήνας CRT και τα αναλογικά περάσματα σήματος μαζί.</string>
|
||||
@@ -1926,6 +1923,7 @@
|
||||
<string name="stores_settings_eyebrow">Ρυθμίσεις</string>
|
||||
<string name="stores_chip_signed_out">(μη συνδεδεμένος)</string>
|
||||
<string name="stores_tab_store">Κατάστημα</string>
|
||||
<string name="stores_tab_installed">Εγκατεστημένα</string>
|
||||
<string name="stores_tab_library">Βιβλιοθήκη</string>
|
||||
<string name="stores_tab_all">Όλα</string>
|
||||
<string name="stores_tab_downloads">Λήψεις</string>
|
||||
@@ -1937,8 +1935,10 @@
|
||||
<string name="stores_signout_stay">Παραμονή συνδεδεμένου</string>
|
||||
<string name="stores_signout_go">Αποσύνδεση</string>
|
||||
<string name="stores_search_catalog">Αναζήτηση στον κατάλογο %1$s…</string>
|
||||
<string name="stores_search_installed">Αναζήτηση εγκατεστημένων παιχνιδιών…</string>
|
||||
<string name="stores_search_library">Αναζήτηση στη βιβλιοθήκη σας…</string>
|
||||
<string name="stores_search_open">Αναζήτηση</string>
|
||||
<string name="stores_installed_empty">Τίποτα εγκατεστημένο</string>
|
||||
<string name="stores_library_loading">Φόρτωση</string>
|
||||
<string name="stores_nothing_matches">Δεν υπάρχουν παιχνίδια</string>
|
||||
<string name="stores_shelves_loading">Φόρτωση του καταστήματος…</string>
|
||||
@@ -1957,19 +1957,7 @@
|
||||
<string name="stores_view_on">Προβολή στο %1$s</string>
|
||||
<string name="stores_buy_price">Αγορά · %1$s</string>
|
||||
<string name="stores_installed_chip">Εγκατεστημένο</string>
|
||||
<string name="stores_section_installed">Εγκατεστημένο</string>
|
||||
<string name="stores_section_not_installed">Δεν είναι εγκατεστημένο</string>
|
||||
<string name="stores_in_library">Στη βιβλιοθήκη</string>
|
||||
<string name="stores_itch_key">Κλειδί API</string>
|
||||
<string name="stores_itch_get_key">Λήψη κλειδιού</string>
|
||||
<string name="stores_itch_key_rejected">Το κλειδί απορρίφθηκε</string>
|
||||
<string name="stores_itch_not_owned">Δεν είναι στη βιβλιοθήκη σας στο itch.io</string>
|
||||
<string name="stores_itch_no_upload">Δεν υπάρχει λήψη για Windows για αυτό το παιχνίδι</string>
|
||||
<string name="stores_itch_no_exe">Δεν βρέθηκε παιχνίδι Windows στη λήψη</string>
|
||||
<string name="stores_itch_extract_failed">Δεν ήταν δυνατή η αποσυμπίεση της λήψης (κωδικός %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">Το itch.io δεν απάντησε</string>
|
||||
<string name="stores_itch_archive">Τα αρχεία %1$s δεν υποστηρίζονται</string>
|
||||
<string name="stores_itch_download_failed">Το itch.io απάντησε HTTP %1$d</string>
|
||||
<string name="stores_free">Δωρεάν</string>
|
||||
<string name="stores_resume">Συνέχιση</string>
|
||||
<string name="stores_refresh">Ανανέωση</string>
|
||||
@@ -1983,7 +1971,6 @@
|
||||
<string name="stores_sort_release_date">Ημερομηνία κυκλοφορίας</string>
|
||||
<string name="stores_view_list">Προβολή λίστας</string>
|
||||
<string name="stores_view_cards">Προβολή καρτών</string>
|
||||
<string name="stores_view_capsules">Προβολή εξωφύλλων</string>
|
||||
<string name="stores_show_mature">Εμφάνιση περιεχομένου για ενήλικες</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
@@ -2079,7 +2066,7 @@
|
||||
<string name="stores_dl_retry">Επανάληψη</string>
|
||||
<string name="stores_dl_clear">Εκκαθάριση</string>
|
||||
<string name="stores_dl_clear_all">Εκκαθάριση ολοκληρωμένων</string>
|
||||
<string name="stores_stage_manifest">Μανιφέστο</string>
|
||||
<string name="stores_stage_manifest">Manifest</string>
|
||||
<string name="stores_stage_download">Λήψη</string>
|
||||
<string name="stores_stage_verify">Επαλήθευση</string>
|
||||
<string name="stores_stage_install">Εγκατάσταση</string>
|
||||
@@ -2260,7 +2247,7 @@
|
||||
<string name="setup_download_source_mirror">Καθρέφτης</string>
|
||||
<string name="setup_download_source_custom">Δικό σου prefix</string>
|
||||
<string name="setup_download_mirror">Ιστότοπος</string>
|
||||
<string name="setup_download_prefix">Πρόθεμα</string>
|
||||
<string name="setup_download_prefix">Prefix</string>
|
||||
<string name="flathub_eyebrow">ΕΦΑΡΜΟΓΕΣ LINUX</string>
|
||||
<string name="flathub_name">Flathub</string>
|
||||
<string name="flathub_line">Εφαρμογές επιφάνειας εργασίας για την επιφάνεια εργασίας Linux, ανοίγουν από την Επιφάνεια εργασίας.</string>
|
||||
@@ -2638,7 +2625,7 @@
|
||||
<string name="storage_failed">Απέτυχε: %1$s</string>
|
||||
<string name="storage_desktop_shared">%1$s κοινά</string>
|
||||
<string name="storage_data_installed">Εγκατεστημένα παιχνίδια</string>
|
||||
<string name="storage_data_parts">Πρόθεμα %1$s · σκιαστές %2$s · εργαστήριο %3$s</string>
|
||||
<string name="storage_data_parts">Prefix %1$s · shaders %2$s · workshop %3$s</string>
|
||||
<string name="storage_clear_shaders">Εκκαθάριση shaders</string>
|
||||
<string name="storage_data_leftovers">Διαγραμμένα παιχνίδια</string>
|
||||
<string name="storage_steam_app">Εφαρμογή Steam %1$d</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Generación de fotogramas</string>
|
||||
<string name="frame_gen_hint">Elige el modo de generación de fotogramas</string>
|
||||
<string name="frame_gen_off">Desactivada</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG adaptativo %1$d</string>
|
||||
<string name="lsfg_needs_steam">Requiere Lossless Scaling en Steam</string>
|
||||
@@ -729,9 +729,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (predeterminada)</string>
|
||||
<string name="setup_theme">Tema</string>
|
||||
<string name="setup_theme_hint">Apariencia del launcher</string>
|
||||
<string name="setup_launcher_icon">Icono de la app</string>
|
||||
<string name="setup_launcher_icon_dark">Oscuro</string>
|
||||
<string name="setup_launcher_icon_light">Claro</string>
|
||||
<string name="setup_home">Usar como pantalla de inicio</string>
|
||||
<string name="setup_home_on">DroidDeck puede ser la app de inicio</string>
|
||||
<string name="setup_home_off">DroidDeck nunca se ofrece como app de inicio</string>
|
||||
@@ -1944,6 +1941,7 @@
|
||||
<string name="stores_settings_eyebrow">Ajustes</string>
|
||||
<string name="stores_chip_signed_out">(sin iniciar sesión)</string>
|
||||
<string name="stores_tab_store">Tienda</string>
|
||||
<string name="stores_tab_installed">Instalados</string>
|
||||
<string name="stores_tab_library">Biblioteca</string>
|
||||
<string name="stores_tab_all">Todos</string>
|
||||
<string name="stores_tab_downloads">Descargas</string>
|
||||
@@ -1955,8 +1953,10 @@
|
||||
<string name="stores_signout_stay">Mantener la sesión</string>
|
||||
<string name="stores_signout_go">Cerrar sesión</string>
|
||||
<string name="stores_search_catalog">Buscar en el catálogo de %1$s…</string>
|
||||
<string name="stores_search_installed">Buscar juegos instalados…</string>
|
||||
<string name="stores_search_library">Buscar en tu biblioteca…</string>
|
||||
<string name="stores_search_open">Buscar</string>
|
||||
<string name="stores_installed_empty">Nada instalado</string>
|
||||
<string name="stores_library_loading">Cargando</string>
|
||||
<string name="stores_nothing_matches">No hay juegos</string>
|
||||
<string name="stores_shelves_loading">Cargando la tienda…</string>
|
||||
@@ -1975,19 +1975,7 @@
|
||||
<string name="stores_view_on">Ver en %1$s</string>
|
||||
<string name="stores_buy_price">Comprar · %1$s</string>
|
||||
<string name="stores_installed_chip">Instalado</string>
|
||||
<string name="stores_section_installed">Instalado</string>
|
||||
<string name="stores_section_not_installed">No instalada</string>
|
||||
<string name="stores_in_library">En la biblioteca</string>
|
||||
<string name="stores_itch_key">Clave de API</string>
|
||||
<string name="stores_itch_get_key">Obtener una clave</string>
|
||||
<string name="stores_itch_key_rejected">Clave rechazada</string>
|
||||
<string name="stores_itch_not_owned">No está en tu biblioteca de itch.io</string>
|
||||
<string name="stores_itch_no_upload">No hay descarga para Windows de este juego</string>
|
||||
<string name="stores_itch_no_exe">No se ha encontrado un juego de Windows en la descarga</string>
|
||||
<string name="stores_itch_extract_failed">No se ha podido extraer la descarga (código %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io no respondió</string>
|
||||
<string name="stores_itch_archive">Los archivos %1$s no son compatibles</string>
|
||||
<string name="stores_itch_download_failed">itch.io respondió HTTP %1$d</string>
|
||||
<string name="stores_free">Gratis</string>
|
||||
<string name="stores_resume">Reanudar</string>
|
||||
<string name="stores_refresh">Actualizar</string>
|
||||
@@ -2001,7 +1989,6 @@
|
||||
<string name="stores_sort_release_date">Fecha de lanzamiento</string>
|
||||
<string name="stores_view_list">Vista de lista</string>
|
||||
<string name="stores_view_cards">Vista de tarjetas</string>
|
||||
<string name="stores_view_capsules">Vista de portadas</string>
|
||||
<string name="stores_show_mature">Mostrar contenido para adultos</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
@@ -2626,39 +2613,32 @@
|
||||
<string name="storage_play_anyway">Jugar igualmente</string>
|
||||
<plurals name="storage_games_installed">
|
||||
<item quantity="one">%d instalado</item>
|
||||
<item quantity="many">%d instalados</item>
|
||||
<item quantity="other">%d instalados</item>
|
||||
</plurals>
|
||||
<plurals name="storage_games_on_sd">
|
||||
<item quantity="one">%d en la tarjeta SD</item>
|
||||
<item quantity="many">%d en la tarjeta SD</item>
|
||||
<item quantity="other">%d en la tarjeta SD</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tools_count">
|
||||
<item quantity="one">%d herramienta</item>
|
||||
<item quantity="many">%d herramientas</item>
|
||||
<item quantity="other">%d herramientas</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_detail">
|
||||
<item quantity="one">Carpetas de prefijo vacías y caché de shaders de %d juego</item>
|
||||
<item quantity="many">Carpetas de prefijo vacías y cachés de shaders de %d juegos</item>
|
||||
<item quantity="other">Carpetas de prefijo vacías y cachés de shaders de %d juegos</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_unsure_detail">
|
||||
<item quantity="one">El prefijo de %d juego aún contiene archivos, que pueden incluir partidas guardadas locales. Haz una copia antes si las quieres.</item>
|
||||
<item quantity="many">Los prefijos de %d juegos aún contienen archivos, que pueden incluir partidas guardadas locales. Haz una copia antes si las quieres.</item>
|
||||
<item quantity="other">Los prefijos de %d juegos aún contienen archivos, que pueden incluir partidas guardadas locales. Haz una copia antes si las quieres.</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tag_games">
|
||||
<item quantity="one">%d juego</item>
|
||||
<item quantity="many">%d juegos</item>
|
||||
<item quantity="other">%d juegos</item>
|
||||
</plurals>
|
||||
<string name="storage_tool_default_proton">Proton predeterminado de DroidDeck</string>
|
||||
<string name="storage_tool_proton">Opción Proton 11 de DroidDeck</string>
|
||||
<plurals name="storage_tool_custom_games">
|
||||
<item quantity="one">Usado por %d juego</item>
|
||||
<item quantity="many">Usado por %d juegos</item>
|
||||
<item quantity="other">Usado por %d juegos</item>
|
||||
</plurals>
|
||||
<string name="storage_legend_components">Componentes</string>
|
||||
@@ -2702,12 +2682,10 @@
|
||||
<string name="storage_flatpak_uninstall_body">Quita la app (%1$s) y cualquier runtime que no use nada más. Sus datos en tu carpeta personal se quedan.</string>
|
||||
<plurals name="storage_components_count">
|
||||
<item quantity="one">%d paquete</item>
|
||||
<item quantity="many">%d paquetes</item>
|
||||
<item quantity="other">%d paquetes</item>
|
||||
</plurals>
|
||||
<plurals name="storage_desktop_apps">
|
||||
<item quantity="one">%d app</item>
|
||||
<item quantity="many">%d apps</item>
|
||||
<item quantity="other">%d apps</item>
|
||||
</plurals>
|
||||
<string name="boot_movie">Vídeo de arranque</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Kehysten generointi</string>
|
||||
<string name="frame_gen_hint">Valitse kehysten generoinnin tila</string>
|
||||
<string name="frame_gen_off">Pois</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Mukautuva %1$d</string>
|
||||
<string name="lsfg_needs_steam">Vaatii Lossless Scalingin Steamista</string>
|
||||
@@ -115,7 +115,7 @@
|
||||
<string name="fex_extreme_detail">Poistaa itseään muokkaavan koodin tarkistukset käytöstä. JIT-pelit ja DRM voivat hajota.</string>
|
||||
<string name="fex_extreme_tso_label">Äärimmäinen + TSO</string>
|
||||
<string name="fex_extreme_tso_detail">Äärimmäiset asetukset, pääasiallinen TSO käytössä.</string>
|
||||
<string name="fex_extreme_gn_label">Äärimmäinen-gn</string>
|
||||
<string name="fex_extreme_gn_label">Extreme-gn</string>
|
||||
<string name="fex_extreme_gn_detail">Suorituskyky TSC- ja metadatavihjeillä. Kokeile ennen Äärimmäistä.</string>
|
||||
<string name="fex_denuvo_label">Denuvo</string>
|
||||
<string name="fex_denuvo_detail">Suorituskyky täysin koodintarkistuksin ja hypervisor-bitti piilotettuna.</string>
|
||||
@@ -299,7 +299,7 @@
|
||||
<string name="mode_decky_installed">Asennettu · %1$s</string>
|
||||
<string name="mode_decky_update">Päivitys saatavilla · %1$s</string>
|
||||
<string name="mode_decky_current">Ajan tasalla · %1$s</string>
|
||||
<string name="mode_decky_loader">Lataaja</string>
|
||||
<string name="mode_decky_loader">Loader</string>
|
||||
<string name="mode_decky_install_latest">Asenna uusin</string>
|
||||
<string name="mode_decky_on">Käynnistyy Steamin kanssa. Päällä ollessaan muut tämän laitteen sovellukset voivat pystyä ohjaamaan Steamia.</string>
|
||||
<string name="mode_decky_off">Pois. Steamin etävianmääritysportti pysyy suljettuna.</string>
|
||||
@@ -729,9 +729,6 @@
|
||||
<string name="setup_app_scale_default">%1$d %% (Oletus)</string>
|
||||
<string name="setup_theme">Teema</string>
|
||||
<string name="setup_theme_hint">Käynnistimen ulkoasu</string>
|
||||
<string name="setup_launcher_icon">Sovelluskuvake</string>
|
||||
<string name="setup_launcher_icon_dark">Tumma</string>
|
||||
<string name="setup_launcher_icon_light">Vaalea</string>
|
||||
<string name="setup_home">Käytä Aloitusnäyttönä</string>
|
||||
<string name="setup_home_on">DroidDeck voi olla kotisovellus</string>
|
||||
<string name="setup_home_off">DroidDeckiä ei koskaan tarjota kotisovellukseksi</string>
|
||||
@@ -1926,6 +1923,7 @@
|
||||
<string name="stores_settings_eyebrow">Asetukset</string>
|
||||
<string name="stores_chip_signed_out">(ei kirjautunut)</string>
|
||||
<string name="stores_tab_store">Kauppa</string>
|
||||
<string name="stores_tab_installed">Asennetut</string>
|
||||
<string name="stores_tab_library">Kirjasto</string>
|
||||
<string name="stores_tab_all">Kaikki</string>
|
||||
<string name="stores_tab_downloads">Lataukset</string>
|
||||
@@ -1937,8 +1935,10 @@
|
||||
<string name="stores_signout_stay">Pysy kirjautuneena</string>
|
||||
<string name="stores_signout_go">Kirjaudu ulos</string>
|
||||
<string name="stores_search_catalog">Hae %1$s-luettelosta…</string>
|
||||
<string name="stores_search_installed">Hae asennetuista peleistä…</string>
|
||||
<string name="stores_search_library">Hae kirjastostasi…</string>
|
||||
<string name="stores_search_open">Hae</string>
|
||||
<string name="stores_installed_empty">Ei mitään asennettuna</string>
|
||||
<string name="stores_library_loading">Latautuu</string>
|
||||
<string name="stores_nothing_matches">Ei pelejä</string>
|
||||
<string name="stores_shelves_loading">Ladataan kauppaa…</string>
|
||||
@@ -1957,19 +1957,7 @@
|
||||
<string name="stores_view_on">Näytä %1$s:ssä</string>
|
||||
<string name="stores_buy_price">Osta · %1$s</string>
|
||||
<string name="stores_installed_chip">Asennettu</string>
|
||||
<string name="stores_section_installed">Asennettu</string>
|
||||
<string name="stores_section_not_installed">Ei asennettu</string>
|
||||
<string name="stores_in_library">Kirjastossa</string>
|
||||
<string name="stores_itch_key">API-avain</string>
|
||||
<string name="stores_itch_get_key">Hae avain</string>
|
||||
<string name="stores_itch_key_rejected">Avain hylätty</string>
|
||||
<string name="stores_itch_not_owned">Ei itch.io-kirjastossasi</string>
|
||||
<string name="stores_itch_no_upload">Ei Windows-latausta tälle pelille</string>
|
||||
<string name="stores_itch_no_exe">Latauksesta ei löytynyt Windows-peliä</string>
|
||||
<string name="stores_itch_extract_failed">Latausta ei voitu purkaa (koodi %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io ei vastannut</string>
|
||||
<string name="stores_itch_archive">%1$s-arkistot eivät ole tuettuja</string>
|
||||
<string name="stores_itch_download_failed">itch.io vastasi HTTP %1$d</string>
|
||||
<string name="stores_free">Ilmainen</string>
|
||||
<string name="stores_resume">Jatka</string>
|
||||
<string name="stores_refresh">Päivitä</string>
|
||||
@@ -1983,7 +1971,6 @@
|
||||
<string name="stores_sort_release_date">Julkaisupäivä</string>
|
||||
<string name="stores_view_list">Luettelonäkymä</string>
|
||||
<string name="stores_view_cards">Korttinäkymä</string>
|
||||
<string name="stores_view_capsules">Kansinäkymä</string>
|
||||
<string name="stores_show_mature">Näytä aikuisille tarkoitettu sisältö</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Génération d\'images</string>
|
||||
<string name="frame_gen_hint">Sélectionnez le mode de génération d\'images à utiliser</string>
|
||||
<string name="frame_gen_off">Désactivé</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptatif %1$d</string>
|
||||
<string name="lsfg_needs_steam">Nécessite Lossless Scaling sur Steam</string>
|
||||
@@ -729,9 +729,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (par défaut)</string>
|
||||
<string name="setup_theme">Thème</string>
|
||||
<string name="setup_theme_hint">Apparence du lanceur</string>
|
||||
<string name="setup_launcher_icon">Icône de l\'app</string>
|
||||
<string name="setup_launcher_icon_dark">Sombre</string>
|
||||
<string name="setup_launcher_icon_light">Clair</string>
|
||||
<string name="setup_home">Utiliser comme écran d\'accueil</string>
|
||||
<string name="setup_home_on">DroidDeck peut être utilisé comme application d\'écran d\'accueil</string>
|
||||
<string name="setup_home_off">DroidDeck ne sera pas proposé comme application d\'écran d\'accueil</string>
|
||||
@@ -1944,6 +1941,7 @@
|
||||
<string name="stores_settings_eyebrow">Paramètres</string>
|
||||
<string name="stores_chip_signed_out">(non connecté)</string>
|
||||
<string name="stores_tab_store">Boutique</string>
|
||||
<string name="stores_tab_installed">Installés</string>
|
||||
<string name="stores_tab_library">Bibliothèque</string>
|
||||
<string name="stores_tab_all">Tous</string>
|
||||
<string name="stores_tab_downloads">Téléchargements</string>
|
||||
@@ -1955,8 +1953,10 @@
|
||||
<string name="stores_signout_stay">Rester connecté</string>
|
||||
<string name="stores_signout_go">Se déconnecter</string>
|
||||
<string name="stores_search_catalog">Rechercher dans le catalogue %1$s…</string>
|
||||
<string name="stores_search_installed">Rechercher dans les jeux installés…</string>
|
||||
<string name="stores_search_library">Rechercher dans votre bibliothèque…</string>
|
||||
<string name="stores_search_open">Rechercher</string>
|
||||
<string name="stores_installed_empty">Aucun jeu installé</string>
|
||||
<string name="stores_library_loading">Chargement</string>
|
||||
<string name="stores_nothing_matches">Aucun jeu</string>
|
||||
<string name="stores_shelves_loading">Chargement de la boutique…</string>
|
||||
@@ -1975,19 +1975,7 @@
|
||||
<string name="stores_view_on">Voir sur %1$s</string>
|
||||
<string name="stores_buy_price">Acheter · %1$s</string>
|
||||
<string name="stores_installed_chip">Installé</string>
|
||||
<string name="stores_section_installed">Installé</string>
|
||||
<string name="stores_section_not_installed">Non installé</string>
|
||||
<string name="stores_in_library">Dans la bibliothèque</string>
|
||||
<string name="stores_itch_key">Clé d\'API</string>
|
||||
<string name="stores_itch_get_key">Obtenir une clé</string>
|
||||
<string name="stores_itch_key_rejected">Clé refusée</string>
|
||||
<string name="stores_itch_not_owned">Absent de votre bibliothèque itch.io</string>
|
||||
<string name="stores_itch_no_upload">Pas de téléchargement Windows pour ce jeu</string>
|
||||
<string name="stores_itch_no_exe">Aucun jeu Windows trouvé dans le téléchargement</string>
|
||||
<string name="stores_itch_extract_failed">Impossible d\'extraire le téléchargement (code %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io n\'a pas répondu</string>
|
||||
<string name="stores_itch_archive">Les archives %1$s ne sont pas prises en charge</string>
|
||||
<string name="stores_itch_download_failed">itch.io a répondu HTTP %1$d</string>
|
||||
<string name="stores_free">Gratuit</string>
|
||||
<string name="stores_resume">Reprendre</string>
|
||||
<string name="stores_refresh">Actualiser</string>
|
||||
@@ -2001,7 +1989,6 @@
|
||||
<string name="stores_sort_release_date">Date de sortie</string>
|
||||
<string name="stores_view_list">Vue liste</string>
|
||||
<string name="stores_view_cards">Vue cartes</string>
|
||||
<string name="stores_view_capsules">Vue jaquettes</string>
|
||||
<string name="stores_show_mature">Afficher le contenu pour adultes</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
@@ -2626,39 +2613,32 @@
|
||||
<string name="storage_play_anyway">Jouer quand même</string>
|
||||
<plurals name="storage_games_installed">
|
||||
<item quantity="one">%d installé</item>
|
||||
<item quantity="many">%d installés</item>
|
||||
<item quantity="other">%d installés</item>
|
||||
</plurals>
|
||||
<plurals name="storage_games_on_sd">
|
||||
<item quantity="one">%d sur la carte SD</item>
|
||||
<item quantity="many">%d sur la carte SD</item>
|
||||
<item quantity="other">%d sur la carte SD</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tools_count">
|
||||
<item quantity="one">%d outil</item>
|
||||
<item quantity="many">%d outils</item>
|
||||
<item quantity="other">%d outils</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_detail">
|
||||
<item quantity="one">Dossiers de préfixe vides et cache de shaders de %d jeu</item>
|
||||
<item quantity="many">Dossiers de préfixe vides et caches de shaders de %d jeux</item>
|
||||
<item quantity="other">Dossiers de préfixe vides et caches de shaders de %d jeux</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_unsure_detail">
|
||||
<item quantity="one">Le préfixe de %d jeu contient encore des fichiers, dont d\'éventuelles sauvegardes locales. Sauvegardez-les avant si vous y tenez.</item>
|
||||
<item quantity="many">Les préfixes de %d jeux contiennent encore des fichiers, dont d\'éventuelles sauvegardes locales. Sauvegardez-les avant si vous y tenez.</item>
|
||||
<item quantity="other">Les préfixes de %d jeux contiennent encore des fichiers, dont d\'éventuelles sauvegardes locales. Sauvegardez-les avant si vous y tenez.</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tag_games">
|
||||
<item quantity="one">%d jeu</item>
|
||||
<item quantity="many">%d jeux</item>
|
||||
<item quantity="other">%d jeux</item>
|
||||
</plurals>
|
||||
<string name="storage_tool_default_proton">Proton par défaut de DroidDeck</string>
|
||||
<string name="storage_tool_proton">Option Proton 11 de DroidDeck</string>
|
||||
<plurals name="storage_tool_custom_games">
|
||||
<item quantity="one">Utilisé par %d jeu</item>
|
||||
<item quantity="many">Utilisé par %d jeux</item>
|
||||
<item quantity="other">Utilisé par %d jeux</item>
|
||||
</plurals>
|
||||
<string name="storage_legend_components">Composants</string>
|
||||
@@ -2702,12 +2682,10 @@
|
||||
<string name="storage_flatpak_uninstall_body">Supprime l\'application (%1$s) et tout environnement d\'exécution que rien d\'autre n\'utilise. Ses données dans votre dossier personnel restent.</string>
|
||||
<plurals name="storage_components_count">
|
||||
<item quantity="one">%d paquet</item>
|
||||
<item quantity="many">%d paquets</item>
|
||||
<item quantity="other">%d paquets</item>
|
||||
</plurals>
|
||||
<plurals name="storage_desktop_apps">
|
||||
<item quantity="one">%d application</item>
|
||||
<item quantity="many">%d applications</item>
|
||||
<item quantity="other">%d applications</item>
|
||||
</plurals>
|
||||
<string name="boot_movie">Vidéo de démarrage</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">फ़्रेम जनरेशन</string>
|
||||
<string name="frame_gen_hint">फ़्रेम जनरेशन मोड चुनें</string>
|
||||
<string name="frame_gen_off">बंद</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG एडैप्टिव %1$d</string>
|
||||
<string name="lsfg_needs_steam">Steam पर Lossless Scaling की ज़रूरत है</string>
|
||||
@@ -729,9 +729,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (डिफ़ॉल्ट)</string>
|
||||
<string name="setup_theme">थीम</string>
|
||||
<string name="setup_theme_hint">लॉन्चर की दिखावट</string>
|
||||
<string name="setup_launcher_icon">ऐप आइकन</string>
|
||||
<string name="setup_launcher_icon_dark">गहरा</string>
|
||||
<string name="setup_launcher_icon_light">हल्का</string>
|
||||
<string name="setup_home">होम स्क्रीन के तौर पर इस्तेमाल करें</string>
|
||||
<string name="setup_home_on">DroidDeck होम ऐप हो सकता है</string>
|
||||
<string name="setup_home_off">DroidDeck को होम ऐप के तौर पर कभी नहीं दिखाया जाता</string>
|
||||
@@ -1897,7 +1894,7 @@
|
||||
<string name="display_sharpness">फ़िल्टर शार्पनेस</string>
|
||||
<string name="display_frame_rate">फ़्रेम रेट</string>
|
||||
<string name="display_look_filter">%1$s स्केलिंग को %2$s पर सेट करता है।</string>
|
||||
<string name="display_match_screen">स्क्रीन से मिलाएँ (%1$d×%2$d)</string>
|
||||
<string name="display_match_screen">Match screen (%1$d×%2$d)</string>
|
||||
<string name="display_follow_screen">Follow screen (खुलने या बंद होने पर आकार बदलता है)</string>
|
||||
<string name="mode_tab_steam">Steam</string>
|
||||
<string name="device_info_show">डिवाइस जानकारी दिखाएं</string>
|
||||
@@ -1926,6 +1923,7 @@
|
||||
<string name="stores_settings_eyebrow">सेटिंग</string>
|
||||
<string name="stores_chip_signed_out">(साइन इन नहीं है)</string>
|
||||
<string name="stores_tab_store">स्टोर</string>
|
||||
<string name="stores_tab_installed">इंस्टॉल किए</string>
|
||||
<string name="stores_tab_library">लाइब्रेरी</string>
|
||||
<string name="stores_tab_all">सभी</string>
|
||||
<string name="stores_tab_downloads">डाउनलोड</string>
|
||||
@@ -1937,8 +1935,10 @@
|
||||
<string name="stores_signout_stay">साइन इन रहें</string>
|
||||
<string name="stores_signout_go">साइन आउट करें</string>
|
||||
<string name="stores_search_catalog">%1$s के कैटलॉग में सर्च करें…</string>
|
||||
<string name="stores_search_installed">इंस्टॉल किए गेम में सर्च करें…</string>
|
||||
<string name="stores_search_library">अपनी लाइब्रेरी में सर्च करें…</string>
|
||||
<string name="stores_search_open">सर्च</string>
|
||||
<string name="stores_installed_empty">कुछ इंस्टॉल नहीं है</string>
|
||||
<string name="stores_library_loading">लोड हो रहा है</string>
|
||||
<string name="stores_nothing_matches">कोई गेम नहीं</string>
|
||||
<string name="stores_shelves_loading">स्टोर लोड हो रहा है…</string>
|
||||
@@ -1957,19 +1957,7 @@
|
||||
<string name="stores_view_on">%1$s पर देखें</string>
|
||||
<string name="stores_buy_price">खरीदें · %1$s</string>
|
||||
<string name="stores_installed_chip">इंस्टॉल है</string>
|
||||
<string name="stores_section_installed">इंस्टॉल है</string>
|
||||
<string name="stores_section_not_installed">इंस्टॉल नहीं है</string>
|
||||
<string name="stores_in_library">लाइब्रेरी में</string>
|
||||
<string name="stores_itch_key">API कुंजी</string>
|
||||
<string name="stores_itch_get_key">कुंजी पाएं</string>
|
||||
<string name="stores_itch_key_rejected">कुंजी अस्वीकृत</string>
|
||||
<string name="stores_itch_not_owned">आपकी itch.io लाइब्रेरी में नहीं</string>
|
||||
<string name="stores_itch_no_upload">इस गेम के लिए Windows डाउनलोड नहीं है</string>
|
||||
<string name="stores_itch_no_exe">डाउनलोड में कोई Windows गेम नहीं मिला</string>
|
||||
<string name="stores_itch_extract_failed">डाउनलोड एक्सट्रैक्ट नहीं हो सका (कोड %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io ने जवाब नहीं दिया</string>
|
||||
<string name="stores_itch_archive">%1$s आर्काइव समर्थित नहीं हैं</string>
|
||||
<string name="stores_itch_download_failed">itch.io ने HTTP %1$d लौटाया</string>
|
||||
<string name="stores_free">फ़्री</string>
|
||||
<string name="stores_resume">फिर से शुरू करें</string>
|
||||
<string name="stores_refresh">रीफ़्रेश करें</string>
|
||||
@@ -1983,7 +1971,6 @@
|
||||
<string name="stores_sort_release_date">रिलीज़ तिथि</string>
|
||||
<string name="stores_view_list">सूची दृश्य</string>
|
||||
<string name="stores_view_cards">कार्ड दृश्य</string>
|
||||
<string name="stores_view_capsules">कवर व्यू</string>
|
||||
<string name="stores_show_mature">वयस्क कंटेंट दिखाएं</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Képkocka-generálás</string>
|
||||
<string name="frame_gen_hint">Válassza ki a képkocka-generálás módját</string>
|
||||
<string name="frame_gen_off">Ki</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG adaptív %1$d</string>
|
||||
<string name="lsfg_needs_steam">A Lossless Scaling szükséges a Steamen</string>
|
||||
@@ -729,9 +729,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (Alapértelmezett)</string>
|
||||
<string name="setup_theme">Téma</string>
|
||||
<string name="setup_theme_hint">Az indítóalkalmazás megjelenése</string>
|
||||
<string name="setup_launcher_icon">Alkalmazásikon</string>
|
||||
<string name="setup_launcher_icon_dark">Sötét</string>
|
||||
<string name="setup_launcher_icon_light">Világos</string>
|
||||
<string name="setup_home">Használat kezdőképernyőként</string>
|
||||
<string name="setup_home_on">A DroidDeck lehet a kezdőképernyő alkalmazás</string>
|
||||
<string name="setup_home_off">A DroidDeck soha nem jelenik meg kezdőképernyő alkalmazásként</string>
|
||||
@@ -1015,7 +1012,7 @@
|
||||
<string name="games_launch_settings">Indítási beállítások</string>
|
||||
<string name="games_launch">Indítás</string>
|
||||
<string name="games_files">Játékfájlok</string>
|
||||
<string name="games_prefix">Proton-prefix</string>
|
||||
<string name="games_prefix">Proton prefix</string>
|
||||
<string name="games_saves_looking">Mentések keresése…</string>
|
||||
<string name="games_saves_none">Nem található mentésmappa név szerint. Az exportálás a teljes felhasználói mappát veszi.</string>
|
||||
<string name="games_saves_folders">%1$d mentésmappa</string>
|
||||
@@ -1926,6 +1923,7 @@
|
||||
<string name="stores_settings_eyebrow">Beállítások</string>
|
||||
<string name="stores_chip_signed_out">(nincs bejelentkezve)</string>
|
||||
<string name="stores_tab_store">Áruház</string>
|
||||
<string name="stores_tab_installed">Telepítve</string>
|
||||
<string name="stores_tab_library">Könyvtár</string>
|
||||
<string name="stores_tab_all">Összes</string>
|
||||
<string name="stores_tab_downloads">Letöltések</string>
|
||||
@@ -1937,8 +1935,10 @@
|
||||
<string name="stores_signout_stay">Bejelentkezve marad</string>
|
||||
<string name="stores_signout_go">Kijelentkezés</string>
|
||||
<string name="stores_search_catalog">Keresés a %1$s katalógusban…</string>
|
||||
<string name="stores_search_installed">Keresés a telepített játékok között…</string>
|
||||
<string name="stores_search_library">Keresés a könyvtárában…</string>
|
||||
<string name="stores_search_open">Keresés</string>
|
||||
<string name="stores_installed_empty">Nincs telepítve semmi</string>
|
||||
<string name="stores_library_loading">Betöltés</string>
|
||||
<string name="stores_nothing_matches">Nincs játék</string>
|
||||
<string name="stores_shelves_loading">Az áruház betöltése…</string>
|
||||
@@ -1957,19 +1957,7 @@
|
||||
<string name="stores_view_on">Megtekintés itt: %1$s</string>
|
||||
<string name="stores_buy_price">Megvétel · %1$s</string>
|
||||
<string name="stores_installed_chip">Telepítve</string>
|
||||
<string name="stores_section_installed">Telepítve</string>
|
||||
<string name="stores_section_not_installed">Nincs telepítve</string>
|
||||
<string name="stores_in_library">A könyvtárban</string>
|
||||
<string name="stores_itch_key">API-kulcs</string>
|
||||
<string name="stores_itch_get_key">Kulcs beszerzése</string>
|
||||
<string name="stores_itch_key_rejected">A kulcs elutasítva</string>
|
||||
<string name="stores_itch_not_owned">Nincs az itch.io-könyvtáradban</string>
|
||||
<string name="stores_itch_no_upload">Ehhez a játékhoz nincs Windows-letöltés</string>
|
||||
<string name="stores_itch_no_exe">A letöltésben nem található Windows-játék</string>
|
||||
<string name="stores_itch_extract_failed">A letöltés nem bontható ki (kód: %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">Az itch.io nem válaszolt</string>
|
||||
<string name="stores_itch_archive">A(z) %1$s archívumok nem támogatottak</string>
|
||||
<string name="stores_itch_download_failed">Az itch.io HTTP %1$d választ adott</string>
|
||||
<string name="stores_free">Ingyenes</string>
|
||||
<string name="stores_resume">Folytatás</string>
|
||||
<string name="stores_refresh">Frissítés</string>
|
||||
@@ -1983,7 +1971,6 @@
|
||||
<string name="stores_sort_release_date">Megjelenés dátuma</string>
|
||||
<string name="stores_view_list">Listanézet</string>
|
||||
<string name="stores_view_cards">Kártyanézet</string>
|
||||
<string name="stores_view_capsules">Borítónézet</string>
|
||||
<string name="stores_show_mature">Felnőtt tartalom megjelenítése</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Pembuatan frame</string>
|
||||
<string name="frame_gen_hint">Pilih mode pembuatan frame</string>
|
||||
<string name="frame_gen_off">Nonaktif</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptif %1$d</string>
|
||||
<string name="lsfg_needs_steam">Memerlukan Lossless Scaling di Steam</string>
|
||||
@@ -726,9 +726,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (Default)</string>
|
||||
<string name="setup_theme">Tema</string>
|
||||
<string name="setup_theme_hint">Tampilan peluncur</string>
|
||||
<string name="setup_launcher_icon">Ikon aplikasi</string>
|
||||
<string name="setup_launcher_icon_dark">Gelap</string>
|
||||
<string name="setup_launcher_icon_light">Terang</string>
|
||||
<string name="setup_home">Gunakan sebagai Layar Utama</string>
|
||||
<string name="setup_home_on">DroidDeck dapat menjadi aplikasi Utama</string>
|
||||
<string name="setup_home_off">DroidDeck tidak pernah ditawarkan sebagai aplikasi Utama</string>
|
||||
@@ -1523,9 +1520,9 @@
|
||||
<string name="ctrl_tint_steam_blue">Biru Steam</string>
|
||||
<string name="ctrl_tint_sky">Langit</string>
|
||||
<string name="ctrl_tint_white">Putih</string>
|
||||
<string name="ctrl_tint_violet">Ungu</string>
|
||||
<string name="ctrl_tint_violet">Violet</string>
|
||||
<string name="ctrl_tint_green">Hijau</string>
|
||||
<string name="ctrl_tint_amber">Ambar</string>
|
||||
<string name="ctrl_tint_amber">Amber</string>
|
||||
<string name="ctrl_tint_red">Merah</string>
|
||||
<string name="ctrl_tint_pink">Merah muda</string>
|
||||
<string name="ctrl_button_a">Tombol A</string>
|
||||
@@ -1748,8 +1745,8 @@
|
||||
<string name="cmgr_list_predates_checksums">Daftar paket ini dibuat sebelum adanya checksum - segarkan dan coba lagi</string>
|
||||
<string name="cmgr_checksum_mismatch">Checksum tidak cocok</string>
|
||||
|
||||
<string name="sprefs_upscaler_linear">Linier</string>
|
||||
<string name="sprefs_upscaler_nearest">Terdekat</string>
|
||||
<string name="sprefs_upscaler_linear">Linear</string>
|
||||
<string name="sprefs_upscaler_nearest">Nearest</string>
|
||||
<string name="sprefs_upscaler_gsr_quality">Snapdragon GSR (kualitas)</string>
|
||||
<string name="sprefs_upscaler_sharpen">Hanya penajaman</string>
|
||||
<string name="sprefs_lod_bias_auto">Otomatis (sesuaikan skala)</string>
|
||||
@@ -1757,7 +1754,7 @@
|
||||
<string name="look_off_desc">Tidak ada yang diterapkan.</string>
|
||||
<string name="look_game_clarity">Kejernihan Gim</string>
|
||||
<string name="look_game_clarity_desc">Penajaman dengan sedikit kontras - cocok untuk sehari-hari.</string>
|
||||
<string name="look_vivid">Cerah</string>
|
||||
<string name="look_vivid">Vivid</string>
|
||||
<string name="look_vivid_desc">Warna didorong lebih kuat. Deband aktif agar langit tidak bergaris.</string>
|
||||
<string name="look_cinematic">Sinematik</string>
|
||||
<string name="look_cinematic_desc">Kontras lebih dalam, warna lebih dingin, tepi dihaluskan.</string>
|
||||
@@ -1905,6 +1902,7 @@
|
||||
<string name="stores_settings_eyebrow">Setelan</string>
|
||||
<string name="stores_chip_signed_out">(belum masuk)</string>
|
||||
<string name="stores_tab_store">Toko</string>
|
||||
<string name="stores_tab_installed">Terinstal</string>
|
||||
<string name="stores_tab_library">Pustaka</string>
|
||||
<string name="stores_tab_all">Semua</string>
|
||||
<string name="stores_tab_downloads">Unduhan</string>
|
||||
@@ -1916,8 +1914,10 @@
|
||||
<string name="stores_signout_stay">Tetap masuk</string>
|
||||
<string name="stores_signout_go">Keluar</string>
|
||||
<string name="stores_search_catalog">Cari katalog %1$s…</string>
|
||||
<string name="stores_search_installed">Cari gim yang terinstal…</string>
|
||||
<string name="stores_search_library">Cari pustaka Anda…</string>
|
||||
<string name="stores_search_open">Cari</string>
|
||||
<string name="stores_installed_empty">Belum ada yang terinstal</string>
|
||||
<string name="stores_library_loading">Memuat</string>
|
||||
<string name="stores_nothing_matches">Tidak ada gim</string>
|
||||
<string name="stores_shelves_loading">Memuat toko…</string>
|
||||
@@ -1936,19 +1936,7 @@
|
||||
<string name="stores_view_on">Lihat di %1$s</string>
|
||||
<string name="stores_buy_price">Beli · %1$s</string>
|
||||
<string name="stores_installed_chip">Terinstal</string>
|
||||
<string name="stores_section_installed">Terinstal</string>
|
||||
<string name="stores_section_not_installed">Belum terinstal</string>
|
||||
<string name="stores_in_library">Dalam pustaka</string>
|
||||
<string name="stores_itch_key">Kunci API</string>
|
||||
<string name="stores_itch_get_key">Dapatkan kunci</string>
|
||||
<string name="stores_itch_key_rejected">Kunci ditolak</string>
|
||||
<string name="stores_itch_not_owned">Tidak ada di pustaka itch.io Anda</string>
|
||||
<string name="stores_itch_no_upload">Tidak ada unduhan Windows untuk game ini</string>
|
||||
<string name="stores_itch_no_exe">Tidak ada game Windows di unduhan</string>
|
||||
<string name="stores_itch_extract_failed">Tidak dapat mengekstrak unduhan (kode %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io tidak menjawab</string>
|
||||
<string name="stores_itch_archive">Arsip %1$s tidak didukung</string>
|
||||
<string name="stores_itch_download_failed">itch.io menjawab HTTP %1$d</string>
|
||||
<string name="stores_free">Gratis</string>
|
||||
<string name="stores_resume">Lanjutkan</string>
|
||||
<string name="stores_refresh">Segarkan</string>
|
||||
@@ -1962,7 +1950,6 @@
|
||||
<string name="stores_sort_release_date">Tanggal rilis</string>
|
||||
<string name="stores_view_list">Tampilan daftar</string>
|
||||
<string name="stores_view_cards">Tampilan kartu</string>
|
||||
<string name="stores_view_capsules">Tampilan sampul</string>
|
||||
<string name="stores_show_mature">Tampilkan konten dewasa</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Generazione di frame</string>
|
||||
<string name="frame_gen_hint">Seleziona la modalità di generazione di frame</string>
|
||||
<string name="frame_gen_off">Disattivata</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG adattivo %1$d</string>
|
||||
<string name="lsfg_needs_steam">Richiede Lossless Scaling su Steam</string>
|
||||
@@ -721,9 +721,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (predefinita)</string>
|
||||
<string name="setup_theme">Tema</string>
|
||||
<string name="setup_theme_hint">Aspetto del launcher</string>
|
||||
<string name="setup_launcher_icon">Icona dell\'app</string>
|
||||
<string name="setup_launcher_icon_dark">Scura</string>
|
||||
<string name="setup_launcher_icon_light">Chiara</string>
|
||||
<string name="setup_home">Usa come schermata Home</string>
|
||||
<string name="setup_home_on">DroidDeck può essere l\'app Home</string>
|
||||
<string name="setup_home_off">DroidDeck non viene mai proposta come app Home</string>
|
||||
@@ -1936,6 +1933,7 @@
|
||||
<string name="stores_settings_eyebrow">Impostazioni</string>
|
||||
<string name="stores_chip_signed_out">(accesso non eseguito)</string>
|
||||
<string name="stores_tab_store">Negozio</string>
|
||||
<string name="stores_tab_installed">Installati</string>
|
||||
<string name="stores_tab_library">Libreria</string>
|
||||
<string name="stores_tab_all">Tutti</string>
|
||||
<string name="stores_tab_downloads">Download</string>
|
||||
@@ -1947,8 +1945,10 @@
|
||||
<string name="stores_signout_stay">Resta connesso</string>
|
||||
<string name="stores_signout_go">Esci</string>
|
||||
<string name="stores_search_catalog">Cerca nel catalogo di %1$s…</string>
|
||||
<string name="stores_search_installed">Cerca tra i giochi installati…</string>
|
||||
<string name="stores_search_library">Cerca nella tua libreria…</string>
|
||||
<string name="stores_search_open">Cerca</string>
|
||||
<string name="stores_installed_empty">Niente di installato</string>
|
||||
<string name="stores_library_loading">Caricamento</string>
|
||||
<string name="stores_nothing_matches">Nessun gioco</string>
|
||||
<string name="stores_shelves_loading">Caricamento del negozio…</string>
|
||||
@@ -1967,19 +1967,7 @@
|
||||
<string name="stores_view_on">Visualizza su %1$s</string>
|
||||
<string name="stores_buy_price">Acquista · %1$s</string>
|
||||
<string name="stores_installed_chip">Installato</string>
|
||||
<string name="stores_section_installed">Installato</string>
|
||||
<string name="stores_section_not_installed">Non installata</string>
|
||||
<string name="stores_in_library">In libreria</string>
|
||||
<string name="stores_itch_key">Chiave API</string>
|
||||
<string name="stores_itch_get_key">Ottieni una chiave</string>
|
||||
<string name="stores_itch_key_rejected">Chiave rifiutata</string>
|
||||
<string name="stores_itch_not_owned">Non è nella tua libreria itch.io</string>
|
||||
<string name="stores_itch_no_upload">Nessun download per Windows di questo gioco</string>
|
||||
<string name="stores_itch_no_exe">Nessun gioco Windows trovato nel download</string>
|
||||
<string name="stores_itch_extract_failed">Impossibile estrarre il download (codice %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io non ha risposto</string>
|
||||
<string name="stores_itch_archive">Gli archivi %1$s non sono supportati</string>
|
||||
<string name="stores_itch_download_failed">itch.io ha risposto HTTP %1$d</string>
|
||||
<string name="stores_free">Gratis</string>
|
||||
<string name="stores_resume">Riprendi</string>
|
||||
<string name="stores_refresh">Aggiorna</string>
|
||||
@@ -1993,7 +1981,6 @@
|
||||
<string name="stores_sort_release_date">Data di uscita</string>
|
||||
<string name="stores_view_list">Visualizzazione elenco</string>
|
||||
<string name="stores_view_cards">Visualizzazione schede</string>
|
||||
<string name="stores_view_capsules">Vista copertine</string>
|
||||
<string name="stores_show_mature">Mostra contenuti per adulti</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
@@ -2626,39 +2613,32 @@
|
||||
<string name="storage_play_anyway">Gioca comunque</string>
|
||||
<plurals name="storage_games_installed">
|
||||
<item quantity="one">%d installato</item>
|
||||
<item quantity="many">%d installati</item>
|
||||
<item quantity="other">%d installati</item>
|
||||
</plurals>
|
||||
<plurals name="storage_games_on_sd">
|
||||
<item quantity="one">%d su scheda SD</item>
|
||||
<item quantity="many">%d su scheda SD</item>
|
||||
<item quantity="other">%d su scheda SD</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tools_count">
|
||||
<item quantity="one">%d strumento</item>
|
||||
<item quantity="many">%d strumenti</item>
|
||||
<item quantity="other">%d strumenti</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_detail">
|
||||
<item quantity="one">Cartelle di prefisso vuote e cache shader di %d gioco</item>
|
||||
<item quantity="many">Cartelle di prefisso vuote e cache shader di %d giochi</item>
|
||||
<item quantity="other">Cartelle di prefisso vuote e cache shader di %d giochi</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_unsure_detail">
|
||||
<item quantity="one">Il prefisso di %d gioco contiene ancora file, che possono includere salvataggi locali. Se ti servono, fanne una copia prima.</item>
|
||||
<item quantity="many">I prefissi di %d giochi contengono ancora file, che possono includere salvataggi locali. Se ti servono, fanne una copia prima.</item>
|
||||
<item quantity="other">I prefissi di %d giochi contengono ancora file, che possono includere salvataggi locali. Se ti servono, fanne una copia prima.</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tag_games">
|
||||
<item quantity="one">%d gioco</item>
|
||||
<item quantity="many">%d giochi</item>
|
||||
<item quantity="other">%d giochi</item>
|
||||
</plurals>
|
||||
<string name="storage_tool_default_proton">Proton predefinito di DroidDeck</string>
|
||||
<string name="storage_tool_proton">Opzione Proton 11 di DroidDeck</string>
|
||||
<plurals name="storage_tool_custom_games">
|
||||
<item quantity="one">Usato da %d gioco</item>
|
||||
<item quantity="many">Usato da %d giochi</item>
|
||||
<item quantity="other">Usato da %d giochi</item>
|
||||
</plurals>
|
||||
<string name="storage_legend_components">Componenti</string>
|
||||
@@ -2702,12 +2682,10 @@
|
||||
<string name="storage_flatpak_uninstall_body">Rimuove l\'app (%1$s) e i runtime che nessun altro usa. I suoi dati nella cartella home restano.</string>
|
||||
<plurals name="storage_components_count">
|
||||
<item quantity="one">%d pacchetto</item>
|
||||
<item quantity="many">%d pacchetti</item>
|
||||
<item quantity="other">%d pacchetti</item>
|
||||
</plurals>
|
||||
<plurals name="storage_desktop_apps">
|
||||
<item quantity="one">%d app</item>
|
||||
<item quantity="many">%d app</item>
|
||||
<item quantity="other">%d app</item>
|
||||
</plurals>
|
||||
<string name="boot_movie">Video di avvio</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">フレーム生成</string>
|
||||
<string name="frame_gen_hint">使用するフレーム生成モードを選択してください</string>
|
||||
<string name="frame_gen_off">オフ</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG アダプティブ %1$d</string>
|
||||
<string name="lsfg_needs_steam">Steam版のLossless Scalingが必要です</string>
|
||||
@@ -726,9 +726,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%%(デフォルト)</string>
|
||||
<string name="setup_theme">テーマ</string>
|
||||
<string name="setup_theme_hint">ランチャーの外観</string>
|
||||
<string name="setup_launcher_icon">アプリアイコン</string>
|
||||
<string name="setup_launcher_icon_dark">ダーク</string>
|
||||
<string name="setup_launcher_icon_light">ライト</string>
|
||||
<string name="setup_home">ホーム画面として使用</string>
|
||||
<string name="setup_home_on">DroidDeckをホームアプリとして使用できます</string>
|
||||
<string name="setup_home_off">DroidDeckはホームアプリとして提供されません</string>
|
||||
@@ -1905,6 +1902,7 @@
|
||||
<string name="stores_settings_eyebrow">設定</string>
|
||||
<string name="stores_chip_signed_out">(未ログイン)</string>
|
||||
<string name="stores_tab_store">ストア</string>
|
||||
<string name="stores_tab_installed">インストール済み</string>
|
||||
<string name="stores_tab_library">ライブラリ</string>
|
||||
<string name="stores_tab_all">すべて</string>
|
||||
<string name="stores_tab_downloads">ダウンロード</string>
|
||||
@@ -1916,8 +1914,10 @@
|
||||
<string name="stores_signout_stay">ログインしたままにする</string>
|
||||
<string name="stores_signout_go">ログアウト</string>
|
||||
<string name="stores_search_catalog">%1$sのカタログを検索…</string>
|
||||
<string name="stores_search_installed">インストール済みのゲームを検索…</string>
|
||||
<string name="stores_search_library">ライブラリを検索…</string>
|
||||
<string name="stores_search_open">検索</string>
|
||||
<string name="stores_installed_empty">何もインストールされていません</string>
|
||||
<string name="stores_library_loading">読み込み中</string>
|
||||
<string name="stores_nothing_matches">ゲームがありません</string>
|
||||
<string name="stores_shelves_loading">ストアを読み込み中…</string>
|
||||
@@ -1936,19 +1936,7 @@
|
||||
<string name="stores_view_on">%1$sで見る</string>
|
||||
<string name="stores_buy_price">購入 · %1$s</string>
|
||||
<string name="stores_installed_chip">インストール済み</string>
|
||||
<string name="stores_section_installed">インストール済み</string>
|
||||
<string name="stores_section_not_installed">未インストール</string>
|
||||
<string name="stores_in_library">ライブラリにあり</string>
|
||||
<string name="stores_itch_key">APIキー</string>
|
||||
<string name="stores_itch_get_key">キーを取得</string>
|
||||
<string name="stores_itch_key_rejected">キーが拒否されました</string>
|
||||
<string name="stores_itch_not_owned">itch.io ライブラリにありません</string>
|
||||
<string name="stores_itch_no_upload">このゲームには Windows 版のダウンロードがありません</string>
|
||||
<string name="stores_itch_no_exe">ダウンロードに Windows 版のゲームが見つかりません</string>
|
||||
<string name="stores_itch_extract_failed">ダウンロードを展開できませんでした (終了コード %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io から応答がありません</string>
|
||||
<string name="stores_itch_archive">%1$s 形式のアーカイブには対応していません</string>
|
||||
<string name="stores_itch_download_failed">itch.io が HTTP %1$d を返しました</string>
|
||||
<string name="stores_free">無料</string>
|
||||
<string name="stores_resume">再開</string>
|
||||
<string name="stores_refresh">更新</string>
|
||||
@@ -1962,7 +1950,6 @@
|
||||
<string name="stores_sort_release_date">リリース日</string>
|
||||
<string name="stores_view_list">リスト表示</string>
|
||||
<string name="stores_view_cards">カード表示</string>
|
||||
<string name="stores_view_capsules">パッケージ表示</string>
|
||||
<string name="stores_show_mature">成人向けコンテンツを表示</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">프레임 생성</string>
|
||||
<string name="frame_gen_hint">사용할 프레임 생성 모드를 선택하세요</string>
|
||||
<string name="frame_gen_off">끔</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG 적응형 %1$d</string>
|
||||
<string name="lsfg_needs_steam">Steam에서 Lossless Scaling이 필요합니다</string>
|
||||
@@ -726,9 +726,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%%(기본값)</string>
|
||||
<string name="setup_theme">테마</string>
|
||||
<string name="setup_theme_hint">런처 외관</string>
|
||||
<string name="setup_launcher_icon">앱 아이콘</string>
|
||||
<string name="setup_launcher_icon_dark">다크</string>
|
||||
<string name="setup_launcher_icon_light">라이트</string>
|
||||
<string name="setup_home">홈 화면으로 사용</string>
|
||||
<string name="setup_home_on">DroidDeck을 홈 앱으로 사용할 수 있습니다</string>
|
||||
<string name="setup_home_off">DroidDeck은 홈 앱으로 제공되지 않습니다</string>
|
||||
@@ -1905,6 +1902,7 @@
|
||||
<string name="stores_settings_eyebrow">설정</string>
|
||||
<string name="stores_chip_signed_out">(로그인 안 됨)</string>
|
||||
<string name="stores_tab_store">스토어</string>
|
||||
<string name="stores_tab_installed">설치됨</string>
|
||||
<string name="stores_tab_library">라이브러리</string>
|
||||
<string name="stores_tab_all">전체</string>
|
||||
<string name="stores_tab_downloads">다운로드</string>
|
||||
@@ -1916,8 +1914,10 @@
|
||||
<string name="stores_signout_stay">로그인 유지</string>
|
||||
<string name="stores_signout_go">로그아웃</string>
|
||||
<string name="stores_search_catalog">%1$s 카탈로그 검색…</string>
|
||||
<string name="stores_search_installed">설치된 게임 검색…</string>
|
||||
<string name="stores_search_library">내 라이브러리 검색…</string>
|
||||
<string name="stores_search_open">검색</string>
|
||||
<string name="stores_installed_empty">설치된 항목 없음</string>
|
||||
<string name="stores_library_loading">불러오는 중</string>
|
||||
<string name="stores_nothing_matches">게임 없음</string>
|
||||
<string name="stores_shelves_loading">스토어 불러오는 중…</string>
|
||||
@@ -1936,19 +1936,7 @@
|
||||
<string name="stores_view_on">%1$s에서 보기</string>
|
||||
<string name="stores_buy_price">구매 · %1$s</string>
|
||||
<string name="stores_installed_chip">설치됨</string>
|
||||
<string name="stores_section_installed">설치됨</string>
|
||||
<string name="stores_section_not_installed">설치되지 않음</string>
|
||||
<string name="stores_in_library">라이브러리에 있음</string>
|
||||
<string name="stores_itch_key">API 키</string>
|
||||
<string name="stores_itch_get_key">키 받기</string>
|
||||
<string name="stores_itch_key_rejected">키가 거부되었습니다</string>
|
||||
<string name="stores_itch_not_owned">itch.io 라이브러리에 없습니다</string>
|
||||
<string name="stores_itch_no_upload">이 게임의 Windows 다운로드가 없습니다</string>
|
||||
<string name="stores_itch_no_exe">다운로드에서 Windows 게임을 찾을 수 없습니다</string>
|
||||
<string name="stores_itch_extract_failed">다운로드한 파일의 압축을 풀 수 없습니다(코드 %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io가 응답하지 않았습니다</string>
|
||||
<string name="stores_itch_archive">%1$s 압축 파일은 지원하지 않습니다</string>
|
||||
<string name="stores_itch_download_failed">itch.io가 HTTP %1$d을(를) 반환했습니다</string>
|
||||
<string name="stores_free">무료</string>
|
||||
<string name="stores_resume">재개</string>
|
||||
<string name="stores_refresh">새로 고침</string>
|
||||
@@ -1962,7 +1950,6 @@
|
||||
<string name="stores_sort_release_date">출시일</string>
|
||||
<string name="stores_view_list">목록 보기</string>
|
||||
<string name="stores_view_cards">카드 보기</string>
|
||||
<string name="stores_view_capsules">표지 보기</string>
|
||||
<string name="stores_show_mature">성인용 콘텐츠 표시</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Penjanaan bingkai</string>
|
||||
<string name="frame_gen_hint">Pilih mod penjanaan bingkai</string>
|
||||
<string name="frame_gen_off">Mati</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptif %1$d</string>
|
||||
<string name="lsfg_needs_steam">Memerlukan Lossless Scaling pada Steam</string>
|
||||
@@ -53,7 +53,7 @@
|
||||
<string name="second_library_import_hint">Menambah pustaka Steam dan mengimbasnya untuk permainan Windows. Permainan yang dimiliki menggunakan entri Steam masing-masing; selebihnya menjadi pintasan bukan Steam.</string>
|
||||
<string name="game_env_title">Persekitaran permainan</string>
|
||||
<string name="game_env_hint">Pembolehubah persekitaran untuk permainan Proton.</string>
|
||||
<string name="game_env_edit">Sunting</string>
|
||||
<string name="game_env_edit">Edit</string>
|
||||
<string name="game_env_shared">Semua permainan Proton</string>
|
||||
<string name="game_env_scope">Profil</string>
|
||||
<string name="game_env_app_id">ID apl Steam</string>
|
||||
@@ -153,7 +153,7 @@
|
||||
<string name="user_apps_update_failed">Mengemas kini apl gagal</string>
|
||||
<string name="user_apps_icon_failed">Ikon itu tidak dapat dibaca</string>
|
||||
<string name="user_apps_saved">%1$s disimpan</string>
|
||||
<string name="user_apps_edit">Sunting</string>
|
||||
<string name="user_apps_edit">Edit</string>
|
||||
<string name="user_apps_check_updates">Semak kemas kini</string>
|
||||
<string name="user_apps_checking_updates">Menyemak kemas kini…</string>
|
||||
<string name="user_apps_update_to">Kemas kini kepada %1$s</string>
|
||||
@@ -205,7 +205,7 @@
|
||||
<string name="add_app_icon_suggestions">Daripada repositori</string>
|
||||
<string name="add_app_icon_searching">Mencari ikon…</string>
|
||||
<string name="add_app_icon_reset">Lalai</string>
|
||||
<string name="edit_app_title">Sunting %1$s</string>
|
||||
<string name="edit_app_title">Edit %1$s</string>
|
||||
<string name="edit_app_save">Simpan</string>
|
||||
<string name="add_app_checking_folder">Menyemak folder…</string>
|
||||
<string name="add_app_folder_linked">Foldernya dipautkan ke dalam sesi: %1$s</string>
|
||||
@@ -315,7 +315,7 @@
|
||||
<string name="mode_added_art">Karya seni</string>
|
||||
<string name="mode_sgdb_key">Kunci API SteamGridDB</string>
|
||||
<string name="mode_sgdb_key_yours">Kunci anda</string>
|
||||
<string name="mode_sgdb_edit">Sunting…</string>
|
||||
<string name="mode_sgdb_edit">Edit…</string>
|
||||
<string name="mode_added_art_hint">Melengkapkan karya seni yang hilang daripada Steam, kemudian SteamGridDB. cover.jpg, hero.jpg, logo.png atau icon.png anda sendiri dalam folder permainan diutamakan.</string>
|
||||
<string name="summary_remove_game">Alih keluar %1$s</string>
|
||||
<string name="mode_added_remove_confirm">Alih keluar?</string>
|
||||
@@ -726,9 +726,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (Lalai)</string>
|
||||
<string name="setup_theme">Tema</string>
|
||||
<string name="setup_theme_hint">Rupa pelancar</string>
|
||||
<string name="setup_launcher_icon">Ikon apl</string>
|
||||
<string name="setup_launcher_icon_dark">Gelap</string>
|
||||
<string name="setup_launcher_icon_light">Cerah</string>
|
||||
<string name="setup_home">Gunakan sebagai skrin Utama</string>
|
||||
<string name="setup_home_on">DroidDeck boleh menjadi apl Utama</string>
|
||||
<string name="setup_home_off">DroidDeck tidak sekali-kali ditawarkan sebagai apl Utama</string>
|
||||
@@ -1523,7 +1520,7 @@
|
||||
<string name="ctrl_tint_steam_blue">Biru Steam</string>
|
||||
<string name="ctrl_tint_sky">Langit</string>
|
||||
<string name="ctrl_tint_white">Putih</string>
|
||||
<string name="ctrl_tint_violet">Ungu</string>
|
||||
<string name="ctrl_tint_violet">Violet</string>
|
||||
<string name="ctrl_tint_green">Hijau</string>
|
||||
<string name="ctrl_tint_amber">Ambar</string>
|
||||
<string name="ctrl_tint_red">Merah</string>
|
||||
@@ -1905,6 +1902,7 @@
|
||||
<string name="stores_settings_eyebrow">Tetapan</string>
|
||||
<string name="stores_chip_signed_out">(belum log masuk)</string>
|
||||
<string name="stores_tab_store">Kedai</string>
|
||||
<string name="stores_tab_installed">Dipasang</string>
|
||||
<string name="stores_tab_library">Pustaka</string>
|
||||
<string name="stores_tab_all">Semua</string>
|
||||
<string name="stores_tab_downloads">Muat turun</string>
|
||||
@@ -1916,8 +1914,10 @@
|
||||
<string name="stores_signout_stay">Kekal log masuk</string>
|
||||
<string name="stores_signout_go">Log keluar</string>
|
||||
<string name="stores_search_catalog">Cari katalog %1$s…</string>
|
||||
<string name="stores_search_installed">Cari permainan dipasang…</string>
|
||||
<string name="stores_search_library">Cari pustaka anda…</string>
|
||||
<string name="stores_search_open">Cari</string>
|
||||
<string name="stores_installed_empty">Tiada apa dipasang</string>
|
||||
<string name="stores_library_loading">Memuatkan</string>
|
||||
<string name="stores_nothing_matches">Tiada permainan</string>
|
||||
<string name="stores_shelves_loading">Memuatkan kedai…</string>
|
||||
@@ -1936,19 +1936,7 @@
|
||||
<string name="stores_view_on">Lihat pada %1$s</string>
|
||||
<string name="stores_buy_price">Beli · %1$s</string>
|
||||
<string name="stores_installed_chip">Dipasang</string>
|
||||
<string name="stores_section_installed">Dipasang</string>
|
||||
<string name="stores_section_not_installed">Belum dipasang</string>
|
||||
<string name="stores_in_library">Dalam pustaka</string>
|
||||
<string name="stores_itch_key">Kunci API</string>
|
||||
<string name="stores_itch_get_key">Dapatkan kunci</string>
|
||||
<string name="stores_itch_key_rejected">Kunci ditolak</string>
|
||||
<string name="stores_itch_not_owned">Tiada dalam pustaka itch.io anda</string>
|
||||
<string name="stores_itch_no_upload">Tiada muat turun Windows untuk permainan ini</string>
|
||||
<string name="stores_itch_no_exe">Tiada permainan Windows ditemui dalam muat turun</string>
|
||||
<string name="stores_itch_extract_failed">Tidak dapat mengekstrak muat turun (kod %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io tidak menjawab</string>
|
||||
<string name="stores_itch_archive">Arkib %1$s tidak disokong</string>
|
||||
<string name="stores_itch_download_failed">itch.io menjawab HTTP %1$d</string>
|
||||
<string name="stores_free">Percuma</string>
|
||||
<string name="stores_resume">Sambung</string>
|
||||
<string name="stores_refresh">Segar semula</string>
|
||||
@@ -1962,7 +1950,6 @@
|
||||
<string name="stores_sort_release_date">Tarikh keluaran</string>
|
||||
<string name="stores_view_list">Paparan senarai</string>
|
||||
<string name="stores_view_cards">Paparan kad</string>
|
||||
<string name="stores_view_capsules">Paparan kulit</string>
|
||||
<string name="stores_show_mature">Papar kandungan matang</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Bildegenerering</string>
|
||||
<string name="frame_gen_hint">Velg modus for bildegenerering</string>
|
||||
<string name="frame_gen_off">Av</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptiv %1$d</string>
|
||||
<string name="lsfg_needs_steam">Krever Lossless Scaling på Steam</string>
|
||||
@@ -527,7 +527,7 @@
|
||||
<string name="perf_client_ui">Klientgrensesnitt</string>
|
||||
<string name="perf_glthread">Trådet GL</string>
|
||||
<string name="perf_glthread_hint">Kan gjøre Steam-menyer mer responsive.</string>
|
||||
<string name="perf_zink">Zink: late deskriptorer</string>
|
||||
<string name="perf_zink">Zink: lazy descriptors</string>
|
||||
<string name="perf_zink_hint">Anbefales for drivere uten descriptor buffers.</string>
|
||||
<string name="perf_noglerror">Hopp over GL-feilkontroller</string>
|
||||
<string name="perf_noglerror_hint">Slår av GL-validering per kall.</string>
|
||||
@@ -729,9 +729,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (Standard)</string>
|
||||
<string name="setup_theme">Tema</string>
|
||||
<string name="setup_theme_hint">Launcherens utseende</string>
|
||||
<string name="setup_launcher_icon">Appikon</string>
|
||||
<string name="setup_launcher_icon_dark">Mørk</string>
|
||||
<string name="setup_launcher_icon_light">Lys</string>
|
||||
<string name="setup_home">Bruk som Hjem-skjerm</string>
|
||||
<string name="setup_home_on">DroidDeck kan være Hjem-appen</string>
|
||||
<string name="setup_home_off">DroidDeck tilbys aldri som en Hjem-app</string>
|
||||
@@ -1926,6 +1923,7 @@
|
||||
<string name="stores_settings_eyebrow">Innstillinger</string>
|
||||
<string name="stores_chip_signed_out">(ikke innlogget)</string>
|
||||
<string name="stores_tab_store">Butikk</string>
|
||||
<string name="stores_tab_installed">Installert</string>
|
||||
<string name="stores_tab_library">Bibliotek</string>
|
||||
<string name="stores_tab_all">Alle</string>
|
||||
<string name="stores_tab_downloads">Nedlastinger</string>
|
||||
@@ -1937,8 +1935,10 @@
|
||||
<string name="stores_signout_stay">Fortsett innlogget</string>
|
||||
<string name="stores_signout_go">Logg ut</string>
|
||||
<string name="stores_search_catalog">Søk i %1$s-katalogen…</string>
|
||||
<string name="stores_search_installed">Søk i installerte spill…</string>
|
||||
<string name="stores_search_library">Søk i biblioteket ditt…</string>
|
||||
<string name="stores_search_open">Søk</string>
|
||||
<string name="stores_installed_empty">Ingenting installert</string>
|
||||
<string name="stores_library_loading">Laster</string>
|
||||
<string name="stores_nothing_matches">Ingen spill</string>
|
||||
<string name="stores_shelves_loading">Laster butikken…</string>
|
||||
@@ -1957,19 +1957,7 @@
|
||||
<string name="stores_view_on">Vis på %1$s</string>
|
||||
<string name="stores_buy_price">Kjøp · %1$s</string>
|
||||
<string name="stores_installed_chip">Installert</string>
|
||||
<string name="stores_section_installed">Installert</string>
|
||||
<string name="stores_section_not_installed">Ikke installert</string>
|
||||
<string name="stores_in_library">I biblioteket</string>
|
||||
<string name="stores_itch_key">API-nøkkel</string>
|
||||
<string name="stores_itch_get_key">Hent en nøkkel</string>
|
||||
<string name="stores_itch_key_rejected">Nøkkelen ble avvist</string>
|
||||
<string name="stores_itch_not_owned">Ikke i itch.io-biblioteket ditt</string>
|
||||
<string name="stores_itch_no_upload">Ingen Windows-nedlasting for dette spillet</string>
|
||||
<string name="stores_itch_no_exe">Fant ikke noe Windows-spill i nedlastingen</string>
|
||||
<string name="stores_itch_extract_failed">Kunne ikke pakke ut nedlastingen (kode %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io svarte ikke</string>
|
||||
<string name="stores_itch_archive">%1$s-arkiver støttes ikke</string>
|
||||
<string name="stores_itch_download_failed">itch.io svarte HTTP %1$d</string>
|
||||
<string name="stores_free">Gratis</string>
|
||||
<string name="stores_resume">Gjenoppta</string>
|
||||
<string name="stores_refresh">Oppdater</string>
|
||||
@@ -1983,7 +1971,6 @@
|
||||
<string name="stores_sort_release_date">Utgivelsesdato</string>
|
||||
<string name="stores_view_list">Listevisning</string>
|
||||
<string name="stores_view_cards">Kortvisning</string>
|
||||
<string name="stores_view_capsules">Omslagsvisning</string>
|
||||
<string name="stores_show_mature">Vis innhold for voksne</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Framegeneratie</string>
|
||||
<string name="frame_gen_hint">Kies de modus voor framegeneratie</string>
|
||||
<string name="frame_gen_off">Uit</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG adaptief %1$d</string>
|
||||
<string name="lsfg_needs_steam">Vereist Lossless Scaling op Steam</string>
|
||||
@@ -729,9 +729,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (Standaard)</string>
|
||||
<string name="setup_theme">Thema</string>
|
||||
<string name="setup_theme_hint">Uiterlijk van de launcher</string>
|
||||
<string name="setup_launcher_icon">App-pictogram</string>
|
||||
<string name="setup_launcher_icon_dark">Donker</string>
|
||||
<string name="setup_launcher_icon_light">Licht</string>
|
||||
<string name="setup_home">Als startscherm gebruiken</string>
|
||||
<string name="setup_home_on">DroidDeck kan de startschermapp zijn</string>
|
||||
<string name="setup_home_off">DroidDeck wordt nooit aangeboden als startschermapp</string>
|
||||
@@ -1926,6 +1923,7 @@
|
||||
<string name="stores_settings_eyebrow">Instellingen</string>
|
||||
<string name="stores_chip_signed_out">(niet aangemeld)</string>
|
||||
<string name="stores_tab_store">Winkel</string>
|
||||
<string name="stores_tab_installed">Geïnstalleerd</string>
|
||||
<string name="stores_tab_library">Bibliotheek</string>
|
||||
<string name="stores_tab_all">Alle</string>
|
||||
<string name="stores_tab_downloads">Downloads</string>
|
||||
@@ -1937,8 +1935,10 @@
|
||||
<string name="stores_signout_stay">Aangemeld blijven</string>
|
||||
<string name="stores_signout_go">Afmelden</string>
|
||||
<string name="stores_search_catalog">Zoeken in de catalogus van %1$s…</string>
|
||||
<string name="stores_search_installed">Geïnstalleerde games zoeken…</string>
|
||||
<string name="stores_search_library">Zoeken in je bibliotheek…</string>
|
||||
<string name="stores_search_open">Zoeken</string>
|
||||
<string name="stores_installed_empty">Niets geïnstalleerd</string>
|
||||
<string name="stores_library_loading">Laden</string>
|
||||
<string name="stores_nothing_matches">Geen games</string>
|
||||
<string name="stores_shelves_loading">Winkel laden…</string>
|
||||
@@ -1957,19 +1957,7 @@
|
||||
<string name="stores_view_on">Bekijken op %1$s</string>
|
||||
<string name="stores_buy_price">Kopen · %1$s</string>
|
||||
<string name="stores_installed_chip">Geïnstalleerd</string>
|
||||
<string name="stores_section_installed">Geïnstalleerd</string>
|
||||
<string name="stores_section_not_installed">Niet geïnstalleerd</string>
|
||||
<string name="stores_in_library">In bibliotheek</string>
|
||||
<string name="stores_itch_key">API-sleutel</string>
|
||||
<string name="stores_itch_get_key">Sleutel ophalen</string>
|
||||
<string name="stores_itch_key_rejected">Sleutel geweigerd</string>
|
||||
<string name="stores_itch_not_owned">Niet in je itch.io-bibliotheek</string>
|
||||
<string name="stores_itch_no_upload">Geen Windows-download voor dit spel</string>
|
||||
<string name="stores_itch_no_exe">Geen Windows-game gevonden in de download</string>
|
||||
<string name="stores_itch_extract_failed">Download kan niet worden uitgepakt (code %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io gaf geen antwoord</string>
|
||||
<string name="stores_itch_archive">%1$s-archieven worden niet ondersteund</string>
|
||||
<string name="stores_itch_download_failed">itch.io antwoordde HTTP %1$d</string>
|
||||
<string name="stores_free">Gratis</string>
|
||||
<string name="stores_resume">Hervatten</string>
|
||||
<string name="stores_refresh">Vernieuwen</string>
|
||||
@@ -1983,7 +1971,6 @@
|
||||
<string name="stores_sort_release_date">Releasedatum</string>
|
||||
<string name="stores_view_list">Lijstweergave</string>
|
||||
<string name="stores_view_cards">Kaartweergave</string>
|
||||
<string name="stores_view_capsules">Hoesweergave</string>
|
||||
<string name="stores_show_mature">Inhoud voor volwassenen tonen</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Generowanie klatek</string>
|
||||
<string name="frame_gen_hint">Wybierz tryb generowania klatek</string>
|
||||
<string name="frame_gen_off">Wył.</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG adaptacyjne %1$d</string>
|
||||
<string name="lsfg_needs_steam">Wymaga Lossless Scaling ze Steam</string>
|
||||
@@ -735,9 +735,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (domyślnie)</string>
|
||||
<string name="setup_theme">Motyw</string>
|
||||
<string name="setup_theme_hint">Wygląd aplikacji</string>
|
||||
<string name="setup_launcher_icon">Ikona aplikacji</string>
|
||||
<string name="setup_launcher_icon_dark">Ciemna</string>
|
||||
<string name="setup_launcher_icon_light">Jasna</string>
|
||||
<string name="setup_home">Użyj jako ekranu głównego</string>
|
||||
<string name="setup_home_on">DroidDeck może być aplikacją ekranu głównego</string>
|
||||
<string name="setup_home_off">DroidDeck nigdy nie jest proponowany jako aplikacja ekranu głównego</string>
|
||||
@@ -1968,6 +1965,7 @@
|
||||
<string name="stores_settings_eyebrow">Ustawienia</string>
|
||||
<string name="stores_chip_signed_out">(niezalogowano)</string>
|
||||
<string name="stores_tab_store">Sklep</string>
|
||||
<string name="stores_tab_installed">Zainstalowane</string>
|
||||
<string name="stores_tab_library">Biblioteka</string>
|
||||
<string name="stores_tab_all">Wszystkie</string>
|
||||
<string name="stores_tab_downloads">Pobierania</string>
|
||||
@@ -1979,8 +1977,10 @@
|
||||
<string name="stores_signout_stay">Zostań zalogowany</string>
|
||||
<string name="stores_signout_go">Wyloguj się</string>
|
||||
<string name="stores_search_catalog">Szukaj w katalogu %1$s…</string>
|
||||
<string name="stores_search_installed">Szukaj zainstalowanych gier…</string>
|
||||
<string name="stores_search_library">Szukaj w Twojej bibliotece…</string>
|
||||
<string name="stores_search_open">Szukaj</string>
|
||||
<string name="stores_installed_empty">Nic nie zainstalowano</string>
|
||||
<string name="stores_library_loading">Wczytywanie</string>
|
||||
<string name="stores_nothing_matches">Brak gier</string>
|
||||
<string name="stores_shelves_loading">Wczytywanie sklepu…</string>
|
||||
@@ -1999,19 +1999,7 @@
|
||||
<string name="stores_view_on">Zobacz w %1$s</string>
|
||||
<string name="stores_buy_price">Kup · %1$s</string>
|
||||
<string name="stores_installed_chip">Zainstalowano</string>
|
||||
<string name="stores_section_installed">Zainstalowano</string>
|
||||
<string name="stores_section_not_installed">Nie zainstalowano</string>
|
||||
<string name="stores_in_library">W bibliotece</string>
|
||||
<string name="stores_itch_key">Klucz API</string>
|
||||
<string name="stores_itch_get_key">Pobierz klucz</string>
|
||||
<string name="stores_itch_key_rejected">Klucz odrzucony</string>
|
||||
<string name="stores_itch_not_owned">Nie ma go w Twojej bibliotece itch.io</string>
|
||||
<string name="stores_itch_no_upload">Brak wersji na Windows dla tej gry</string>
|
||||
<string name="stores_itch_no_exe">W pobranych plikach nie znaleziono gry na Windows</string>
|
||||
<string name="stores_itch_extract_failed">Nie można rozpakować pobranego pliku (kod %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io nie odpowiedziało</string>
|
||||
<string name="stores_itch_archive">Archiwa %1$s nie są obsługiwane</string>
|
||||
<string name="stores_itch_download_failed">itch.io odpowiedziało HTTP %1$d</string>
|
||||
<string name="stores_free">Bezpłatna</string>
|
||||
<string name="stores_resume">Wznów</string>
|
||||
<string name="stores_refresh">Odśwież</string>
|
||||
@@ -2025,7 +2013,6 @@
|
||||
<string name="stores_sort_release_date">Data wydania</string>
|
||||
<string name="stores_view_list">Widok listy</string>
|
||||
<string name="stores_view_cards">Widok kart</string>
|
||||
<string name="stores_view_capsules">Widok okładek</string>
|
||||
<string name="stores_show_mature">Pokaż treści dla dorosłych</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Geração de fotogramas</string>
|
||||
<string name="frame_gen_hint">Selecione o modo de geração de fotogramas</string>
|
||||
<string name="frame_gen_off">Desligado</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptativo %1$d</string>
|
||||
<string name="lsfg_needs_steam">Requer o Lossless Scaling no Steam</string>
|
||||
@@ -721,9 +721,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (padrão)</string>
|
||||
<string name="setup_theme">Tema</string>
|
||||
<string name="setup_theme_hint">Aspeto do iniciador</string>
|
||||
<string name="setup_launcher_icon">Ícone da app</string>
|
||||
<string name="setup_launcher_icon_dark">Escuro</string>
|
||||
<string name="setup_launcher_icon_light">Claro</string>
|
||||
<string name="setup_home">Utilizar como página inicial</string>
|
||||
<string name="setup_home_on">O DroidDeck pode ser a app de página inicial</string>
|
||||
<string name="setup_home_off">O DroidDeck nunca é proposto como app de página inicial</string>
|
||||
@@ -1095,10 +1092,10 @@
|
||||
<string name="second_terminal_not_ready">A sessão Linux ainda não está pronta para um terminal.</string>
|
||||
<string name="rail_steam">Steam</string>
|
||||
<string name="rail_games">Jogos</string>
|
||||
<string name="rail_desktop">Ambiente de trabalho</string>
|
||||
<string name="rail_desktop">Desktop</string>
|
||||
<string name="rail_store">Loja</string>
|
||||
<string name="rail_components">Componentes</string>
|
||||
<string name="rail_apps">Aplicações</string>
|
||||
<string name="rail_apps">Apps</string>
|
||||
<string name="rail_setup">Configuração</string>
|
||||
<string name="rail_updates">Atualizações</string>
|
||||
<string name="rail_percent">%1$d%%</string>
|
||||
@@ -1936,6 +1933,7 @@
|
||||
<string name="stores_settings_eyebrow">Definições</string>
|
||||
<string name="stores_chip_signed_out">(sessão não iniciada)</string>
|
||||
<string name="stores_tab_store">Loja</string>
|
||||
<string name="stores_tab_installed">Instalados</string>
|
||||
<string name="stores_tab_library">Biblioteca</string>
|
||||
<string name="stores_tab_all">Todos</string>
|
||||
<string name="stores_tab_downloads">Transferências</string>
|
||||
@@ -1947,8 +1945,10 @@
|
||||
<string name="stores_signout_stay">Manter sessão iniciada</string>
|
||||
<string name="stores_signout_go">Terminar sessão</string>
|
||||
<string name="stores_search_catalog">Pesquisar no catálogo %1$s…</string>
|
||||
<string name="stores_search_installed">Pesquisar jogos instalados…</string>
|
||||
<string name="stores_search_library">Pesquisar na sua biblioteca…</string>
|
||||
<string name="stores_search_open">Pesquisar</string>
|
||||
<string name="stores_installed_empty">Nada instalado</string>
|
||||
<string name="stores_library_loading">A carregar</string>
|
||||
<string name="stores_nothing_matches">Nenhum jogo</string>
|
||||
<string name="stores_shelves_loading">A carregar a loja…</string>
|
||||
@@ -1967,19 +1967,7 @@
|
||||
<string name="stores_view_on">Ver em %1$s</string>
|
||||
<string name="stores_buy_price">Comprar · %1$s</string>
|
||||
<string name="stores_installed_chip">Instalado</string>
|
||||
<string name="stores_section_installed">Instalado</string>
|
||||
<string name="stores_section_not_installed">Não instalado</string>
|
||||
<string name="stores_in_library">Na biblioteca</string>
|
||||
<string name="stores_itch_key">Chave de API</string>
|
||||
<string name="stores_itch_get_key">Obter uma chave</string>
|
||||
<string name="stores_itch_key_rejected">Chave rejeitada</string>
|
||||
<string name="stores_itch_not_owned">Não está na sua biblioteca do itch.io</string>
|
||||
<string name="stores_itch_no_upload">Sem transferência para Windows deste jogo</string>
|
||||
<string name="stores_itch_no_exe">Não foi encontrado nenhum jogo para Windows na transferência</string>
|
||||
<string name="stores_itch_extract_failed">Não foi possível extrair a transferência (código %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">O itch.io não respondeu</string>
|
||||
<string name="stores_itch_archive">Ficheiros %1$s não são suportados</string>
|
||||
<string name="stores_itch_download_failed">O itch.io respondeu HTTP %1$d</string>
|
||||
<string name="stores_free">Grátis</string>
|
||||
<string name="stores_resume">Retomar</string>
|
||||
<string name="stores_refresh">Atualizar</string>
|
||||
@@ -1993,7 +1981,6 @@
|
||||
<string name="stores_sort_release_date">Data de lançamento</string>
|
||||
<string name="stores_view_list">Vista de lista</string>
|
||||
<string name="stores_view_cards">Vista de cartões</string>
|
||||
<string name="stores_view_capsules">Vista de capas</string>
|
||||
<string name="stores_show_mature">Mostrar conteúdo para adultos</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
@@ -2255,7 +2242,7 @@
|
||||
<string name="setup_tab_diagnostics">Diagnóstico</string>
|
||||
<string name="setup_ready">Pronto para jogar</string>
|
||||
<string name="setup_not_ready">Ainda não está pronto para jogar</string>
|
||||
<string name="setup_ready_runtime">ambiente de execução %1$s</string>
|
||||
<string name="setup_ready_runtime">runtime %1$s</string>
|
||||
<string name="setup_ready_limit_off">limite de processos desativado</string>
|
||||
<string name="setup_ready_signed_in">sessão iniciada como %1$s</string>
|
||||
<string name="setup_needs_you">Quando algo precisa de si</string>
|
||||
@@ -2626,39 +2613,32 @@
|
||||
<string name="storage_play_anyway">Jogar mesmo assim</string>
|
||||
<plurals name="storage_games_installed">
|
||||
<item quantity="one">%d instalado</item>
|
||||
<item quantity="many">%d instalados</item>
|
||||
<item quantity="other">%d instalados</item>
|
||||
</plurals>
|
||||
<plurals name="storage_games_on_sd">
|
||||
<item quantity="one">%d no cartão SD</item>
|
||||
<item quantity="many">%d no cartão SD</item>
|
||||
<item quantity="other">%d no cartão SD</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tools_count">
|
||||
<item quantity="one">%d ferramenta</item>
|
||||
<item quantity="many">%d ferramentas</item>
|
||||
<item quantity="other">%d ferramentas</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_detail">
|
||||
<item quantity="one">Pastas de prefixo vazias e cache de shaders de %d jogo</item>
|
||||
<item quantity="many">Pastas de prefixo vazias e cache de shaders de %d jogos</item>
|
||||
<item quantity="other">Pastas de prefixo vazias e cache de shaders de %d jogos</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_unsure_detail">
|
||||
<item quantity="one">O prefixo de %d jogo ainda tem ficheiros, que podem incluir jogos guardados locais. Se precisar deles, faça uma cópia antes.</item>
|
||||
<item quantity="many">Os prefixos de %d jogos ainda têm ficheiros, que podem incluir jogos guardados locais. Se precisar deles, faça uma cópia antes.</item>
|
||||
<item quantity="other">Os prefixos de %d jogos ainda têm ficheiros, que podem incluir jogos guardados locais. Se precisar deles, faça uma cópia antes.</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tag_games">
|
||||
<item quantity="one">%d jogo</item>
|
||||
<item quantity="many">%d jogos</item>
|
||||
<item quantity="other">%d jogos</item>
|
||||
</plurals>
|
||||
<string name="storage_tool_default_proton">Proton predefinido do DroidDeck</string>
|
||||
<string name="storage_tool_proton">Opção Proton 11 do DroidDeck</string>
|
||||
<plurals name="storage_tool_custom_games">
|
||||
<item quantity="one">Usado por %d jogo</item>
|
||||
<item quantity="many">Usado por %d jogos</item>
|
||||
<item quantity="other">Usado por %d jogos</item>
|
||||
</plurals>
|
||||
<string name="storage_legend_components">Componentes</string>
|
||||
@@ -2702,12 +2682,10 @@
|
||||
<string name="storage_flatpak_uninstall_body">Remove a aplicação (%1$s) e os ambientes de execução que mais ninguém usa. Os dados dela na pasta pessoal mantêm-se.</string>
|
||||
<plurals name="storage_components_count">
|
||||
<item quantity="one">%d pacote</item>
|
||||
<item quantity="many">%d pacotes</item>
|
||||
<item quantity="other">%d pacotes</item>
|
||||
</plurals>
|
||||
<plurals name="storage_desktop_apps">
|
||||
<item quantity="one">%d aplicação</item>
|
||||
<item quantity="many">%d aplicações</item>
|
||||
<item quantity="other">%d aplicações</item>
|
||||
</plurals>
|
||||
<string name="boot_movie">Vídeo de arranque</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Geração de quadros</string>
|
||||
<string name="frame_gen_hint">Selecione o modo de geração de quadros</string>
|
||||
<string name="frame_gen_off">Desativada</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptativo %1$d</string>
|
||||
<string name="lsfg_needs_steam">Requer o Lossless Scaling na Steam</string>
|
||||
@@ -721,9 +721,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (padrão)</string>
|
||||
<string name="setup_theme">Tema</string>
|
||||
<string name="setup_theme_hint">Aparência do inicializador</string>
|
||||
<string name="setup_launcher_icon">Ícone do app</string>
|
||||
<string name="setup_launcher_icon_dark">Escuro</string>
|
||||
<string name="setup_launcher_icon_light">Claro</string>
|
||||
<string name="setup_home">Usar como tela inicial</string>
|
||||
<string name="setup_home_on">O DroidDeck pode ser o app de início</string>
|
||||
<string name="setup_home_off">O DroidDeck nunca é oferecido como app de início</string>
|
||||
@@ -1095,7 +1092,7 @@
|
||||
<string name="second_terminal_not_ready">A sessão Linux ainda não está pronta para um terminal.</string>
|
||||
<string name="rail_steam">Steam</string>
|
||||
<string name="rail_games">Jogos</string>
|
||||
<string name="rail_desktop">Área de trabalho</string>
|
||||
<string name="rail_desktop">Desktop</string>
|
||||
<string name="rail_store">Loja</string>
|
||||
<string name="rail_components">Componentes</string>
|
||||
<string name="rail_apps">Apps</string>
|
||||
@@ -1936,6 +1933,7 @@
|
||||
<string name="stores_settings_eyebrow">Configurações</string>
|
||||
<string name="stores_chip_signed_out">(sem login)</string>
|
||||
<string name="stores_tab_store">Loja</string>
|
||||
<string name="stores_tab_installed">Instalados</string>
|
||||
<string name="stores_tab_library">Biblioteca</string>
|
||||
<string name="stores_tab_all">Todos</string>
|
||||
<string name="stores_tab_downloads">Downloads</string>
|
||||
@@ -1947,8 +1945,10 @@
|
||||
<string name="stores_signout_stay">Continuar conectado</string>
|
||||
<string name="stores_signout_go">Sair</string>
|
||||
<string name="stores_search_catalog">Pesquisar no catálogo de %1$s…</string>
|
||||
<string name="stores_search_installed">Pesquisar jogos instalados…</string>
|
||||
<string name="stores_search_library">Pesquisar na sua biblioteca…</string>
|
||||
<string name="stores_search_open">Pesquisar</string>
|
||||
<string name="stores_installed_empty">Nada instalado</string>
|
||||
<string name="stores_library_loading">Carregando</string>
|
||||
<string name="stores_nothing_matches">Nenhum jogo</string>
|
||||
<string name="stores_shelves_loading">Carregando a loja…</string>
|
||||
@@ -1967,19 +1967,7 @@
|
||||
<string name="stores_view_on">Ver em %1$s</string>
|
||||
<string name="stores_buy_price">Comprar · %1$s</string>
|
||||
<string name="stores_installed_chip">Instalado</string>
|
||||
<string name="stores_section_installed">Instalado</string>
|
||||
<string name="stores_section_not_installed">Não instalado</string>
|
||||
<string name="stores_in_library">Na biblioteca</string>
|
||||
<string name="stores_itch_key">Chave de API</string>
|
||||
<string name="stores_itch_get_key">Obter uma chave</string>
|
||||
<string name="stores_itch_key_rejected">Chave rejeitada</string>
|
||||
<string name="stores_itch_not_owned">Não está na sua biblioteca do itch.io</string>
|
||||
<string name="stores_itch_no_upload">Sem download para Windows deste jogo</string>
|
||||
<string name="stores_itch_no_exe">Nenhum jogo para Windows encontrado no download</string>
|
||||
<string name="stores_itch_extract_failed">Não foi possível extrair o download (código %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">O itch.io não respondeu</string>
|
||||
<string name="stores_itch_archive">Arquivos %1$s não são suportados</string>
|
||||
<string name="stores_itch_download_failed">O itch.io respondeu HTTP %1$d</string>
|
||||
<string name="stores_free">Grátis</string>
|
||||
<string name="stores_resume">Retomar</string>
|
||||
<string name="stores_refresh">Atualizar</string>
|
||||
@@ -1993,7 +1981,6 @@
|
||||
<string name="stores_sort_release_date">Data de lançamento</string>
|
||||
<string name="stores_view_list">Vista de lista</string>
|
||||
<string name="stores_view_cards">Vista de cartões</string>
|
||||
<string name="stores_view_capsules">Visualização de capas</string>
|
||||
<string name="stores_show_mature">Mostrar conteúdo adulto</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
@@ -2626,39 +2613,32 @@
|
||||
<string name="storage_play_anyway">Jogar assim mesmo</string>
|
||||
<plurals name="storage_games_installed">
|
||||
<item quantity="one">%d instalado</item>
|
||||
<item quantity="many">%d instalados</item>
|
||||
<item quantity="other">%d instalados</item>
|
||||
</plurals>
|
||||
<plurals name="storage_games_on_sd">
|
||||
<item quantity="one">%d no cartão SD</item>
|
||||
<item quantity="many">%d no cartão SD</item>
|
||||
<item quantity="other">%d no cartão SD</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tools_count">
|
||||
<item quantity="one">%d ferramenta</item>
|
||||
<item quantity="many">%d ferramentas</item>
|
||||
<item quantity="other">%d ferramentas</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_detail">
|
||||
<item quantity="one">Pastas de prefixo vazias e cache de shaders de %d jogo</item>
|
||||
<item quantity="many">Pastas de prefixo vazias e cache de shaders de %d jogos</item>
|
||||
<item quantity="other">Pastas de prefixo vazias e cache de shaders de %d jogos</item>
|
||||
</plurals>
|
||||
<plurals name="storage_item_leftovers_unsure_detail">
|
||||
<item quantity="one">O prefixo de %d jogo ainda tem arquivos, que podem incluir saves locais. Se precisar deles, faça uma cópia antes.</item>
|
||||
<item quantity="many">Os prefixos de %d jogos ainda têm arquivos, que podem incluir saves locais. Se precisar deles, faça uma cópia antes.</item>
|
||||
<item quantity="other">Os prefixos de %d jogos ainda têm arquivos, que podem incluir saves locais. Se precisar deles, faça uma cópia antes.</item>
|
||||
</plurals>
|
||||
<plurals name="storage_tag_games">
|
||||
<item quantity="one">%d jogo</item>
|
||||
<item quantity="many">%d jogos</item>
|
||||
<item quantity="other">%d jogos</item>
|
||||
</plurals>
|
||||
<string name="storage_tool_default_proton">Proton padrão do DroidDeck</string>
|
||||
<string name="storage_tool_proton">Opção Proton 11 do DroidDeck</string>
|
||||
<plurals name="storage_tool_custom_games">
|
||||
<item quantity="one">Usado por %d jogo</item>
|
||||
<item quantity="many">Usado por %d jogos</item>
|
||||
<item quantity="other">Usado por %d jogos</item>
|
||||
</plurals>
|
||||
<string name="storage_legend_components">Componentes</string>
|
||||
@@ -2702,12 +2682,10 @@
|
||||
<string name="storage_flatpak_uninstall_body">Remove o app (%1$s) e os runtimes que mais ninguém usa. Os dados dele na pasta pessoal ficam.</string>
|
||||
<plurals name="storage_components_count">
|
||||
<item quantity="one">%d pacote</item>
|
||||
<item quantity="many">%d pacotes</item>
|
||||
<item quantity="other">%d pacotes</item>
|
||||
</plurals>
|
||||
<plurals name="storage_desktop_apps">
|
||||
<item quantity="one">%d app</item>
|
||||
<item quantity="many">%d apps</item>
|
||||
<item quantity="other">%d apps</item>
|
||||
</plurals>
|
||||
<string name="boot_movie">Vídeo de inicialização</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="frame_gen_title">Generare de cadre</string>
|
||||
<string name="frame_gen_hint">Selectează modul de generare a cadrelor</string>
|
||||
<string name="frame_gen_off">Dezactivat</string>
|
||||
<string name="frame_gen_ddfg">DD-FG %1$d×</string>
|
||||
<string name="frame_gen_winfg">Win-FG %1$d×</string>
|
||||
<string name="frame_gen_lsfg">LSFG %1$d×</string>
|
||||
<string name="frame_gen_lsfg_adaptive">LSFG Adaptiv %1$d</string>
|
||||
<string name="lsfg_needs_steam">Necesită Lossless Scaling pe Steam</string>
|
||||
@@ -732,9 +732,6 @@
|
||||
<string name="setup_app_scale_default">%1$d%% (Implicit)</string>
|
||||
<string name="setup_theme">Temă</string>
|
||||
<string name="setup_theme_hint">Aspectul launcherului</string>
|
||||
<string name="setup_launcher_icon">Pictograma aplicației</string>
|
||||
<string name="setup_launcher_icon_dark">Întunecată</string>
|
||||
<string name="setup_launcher_icon_light">Luminoasă</string>
|
||||
<string name="setup_home">Folosește ca ecran Acasă</string>
|
||||
<string name="setup_home_on">DroidDeck poate fi aplicația Acasă</string>
|
||||
<string name="setup_home_off">DroidDeck nu este niciodată oferit ca aplicație Acasă</string>
|
||||
@@ -1445,8 +1442,8 @@
|
||||
<string name="fm_pin_current">Fixează folderul curent</string>
|
||||
<string name="fm_no_favorites">Niciun favorit încă - fixează un folder din meniul său ⋮ pentru a reveni rapid aici.</string>
|
||||
<string name="fm_unpin_favorite">Elimină din favorite</string>
|
||||
<string name="fm_size_zero">0 octeți</string>
|
||||
<string name="fm_unit_bytes">octeți</string>
|
||||
<string name="fm_size_zero">0 bytes</string>
|
||||
<string name="fm_unit_bytes">bytes</string>
|
||||
<string name="fm_prop_location">Locație</string>
|
||||
<string name="fm_prop_type">Tip</string>
|
||||
<string name="fm_prop_size">Dimensiune</string>
|
||||
@@ -1565,7 +1562,7 @@
|
||||
<string name="ctrl_tint_white">Alb</string>
|
||||
<string name="ctrl_tint_violet">Violet</string>
|
||||
<string name="ctrl_tint_green">Verde</string>
|
||||
<string name="ctrl_tint_amber">Chihlimbariu</string>
|
||||
<string name="ctrl_tint_amber">Amber</string>
|
||||
<string name="ctrl_tint_red">Roșu</string>
|
||||
<string name="ctrl_tint_pink">Roz</string>
|
||||
<string name="ctrl_button_a">Butonul A</string>
|
||||
@@ -1801,7 +1798,7 @@
|
||||
<string name="look_game_clarity_desc">Claritate cu puțin contrast - cea de zi cu zi.</string>
|
||||
<string name="look_vivid">Viu</string>
|
||||
<string name="look_vivid_desc">Culoare intensificată puternic. Eliminarea benzilor activată, ca cerurile să nu aibă dungi.</string>
|
||||
<string name="look_cinematic">Cinematografic</string>
|
||||
<string name="look_cinematic">Cinematic</string>
|
||||
<string name="look_cinematic_desc">Contrast mai profund, culoare mai rece, margini netezite.</string>
|
||||
<string name="look_competitive">Competitiv</string>
|
||||
<string name="look_competitive_desc">Umbre ridicate și claritate puternică - observă persoanele mai întâi.</string>
|
||||
@@ -1947,6 +1944,7 @@
|
||||
<string name="stores_settings_eyebrow">Setări</string>
|
||||
<string name="stores_chip_signed_out">(neautentificat)</string>
|
||||
<string name="stores_tab_store">Magazin</string>
|
||||
<string name="stores_tab_installed">Instalate</string>
|
||||
<string name="stores_tab_library">Bibliotecă</string>
|
||||
<string name="stores_tab_all">Toate</string>
|
||||
<string name="stores_tab_downloads">Descărcări</string>
|
||||
@@ -1958,8 +1956,10 @@
|
||||
<string name="stores_signout_stay">Rămâi autentificat</string>
|
||||
<string name="stores_signout_go">Deconectare</string>
|
||||
<string name="stores_search_catalog">Caută în catalogul %1$s…</string>
|
||||
<string name="stores_search_installed">Caută jocurile instalate…</string>
|
||||
<string name="stores_search_library">Caută în biblioteca ta…</string>
|
||||
<string name="stores_search_open">Căutare</string>
|
||||
<string name="stores_installed_empty">Nimic instalat</string>
|
||||
<string name="stores_library_loading">Se încarcă</string>
|
||||
<string name="stores_nothing_matches">Niciun joc</string>
|
||||
<string name="stores_shelves_loading">Se încarcă magazinul…</string>
|
||||
@@ -1978,19 +1978,7 @@
|
||||
<string name="stores_view_on">Vezi pe %1$s</string>
|
||||
<string name="stores_buy_price">Cumpără · %1$s</string>
|
||||
<string name="stores_installed_chip">Instalat</string>
|
||||
<string name="stores_section_installed">Instalat</string>
|
||||
<string name="stores_section_not_installed">Neinstalat</string>
|
||||
<string name="stores_in_library">În bibliotecă</string>
|
||||
<string name="stores_itch_key">Cheie API</string>
|
||||
<string name="stores_itch_get_key">Obține o cheie</string>
|
||||
<string name="stores_itch_key_rejected">Cheie respinsă</string>
|
||||
<string name="stores_itch_not_owned">Nu este în biblioteca ta itch.io</string>
|
||||
<string name="stores_itch_no_upload">Nu există descărcare pentru Windows la acest joc</string>
|
||||
<string name="stores_itch_no_exe">Nu s-a găsit niciun joc Windows în descărcare</string>
|
||||
<string name="stores_itch_extract_failed">Descărcarea nu a putut fi extrasă (cod %1$d)</string>
|
||||
<string name="stores_itch_no_catalog">itch.io nu a răspuns</string>
|
||||
<string name="stores_itch_archive">Arhivele %1$s nu sunt acceptate</string>
|
||||
<string name="stores_itch_download_failed">itch.io a răspuns HTTP %1$d</string>
|
||||
<string name="stores_free">Gratuit</string>
|
||||
<string name="stores_resume">Reluare</string>
|
||||
<string name="stores_refresh">Reîmprospătează</string>
|
||||
@@ -2004,7 +1992,6 @@
|
||||
<string name="stores_sort_release_date">Data lansării</string>
|
||||
<string name="stores_view_list">Vizualizare listă</string>
|
||||
<string name="stores_view_cards">Vizualizare carduri</string>
|
||||
<string name="stores_view_capsules">Vizualizare coperți</string>
|
||||
<string name="stores_show_mature">Arată conținutul pentru adulți</string>
|
||||
<string name="epic_card_title">Epic</string>
|
||||
<string name="epic_card_eos">EOS</string>
|
||||
|
||||