mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-23 07:01:01 +02:00
499 lines
19 KiB
C++
499 lines
19 KiB
C++
#include "libc_shim/vulkan_etc1_sky_transcoder.h"
|
|
|
|
#include <array>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <vector>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
struct AAssetManager;
|
|
struct AAsset;
|
|
|
|
extern "C" {
|
|
AAssetManager* AAssetManager_fromJava(void* env, void* assetManager);
|
|
AAsset* AAssetManager_open(AAssetManager* mgr, const char* filename, int mode);
|
|
void AAsset_close(AAsset* asset);
|
|
int AAsset_read(AAsset* asset, void* buf, std::size_t count);
|
|
const void* AAsset_getBuffer(AAsset* asset);
|
|
off_t AAsset_getLength(AAsset* asset);
|
|
off_t AAsset_getRemainingLength(AAsset* asset);
|
|
off_t AAsset_seek(AAsset* asset, off_t offset, int whence);
|
|
int AAsset_openFileDescriptor(AAsset* asset, off_t* outStart,
|
|
off_t* outLength);
|
|
int AAsset_openFileDescriptor64(AAsset* asset, off_t* outStart,
|
|
off_t* outLength);
|
|
}
|
|
|
|
namespace {
|
|
|
|
std::vector<unsigned char> MakeEtc1SkyTexture() {
|
|
std::vector<unsigned char> data = {
|
|
0xab, 0x4b, 0x54, 0x58, 0x20, 0x31, 0x31, 0xbb, 0x0d, 0x0a, 0x1a, 0x0a,
|
|
};
|
|
// KTX1: one 4x4 ETC1 mip and its byte count.
|
|
for (uint32_t value : {0x04030201U, 0U, 1U, 0U, 0x8d64U, 0x1907U,
|
|
4U, 4U, 0U, 0U, 1U, 1U, 0U, 8U}) {
|
|
for (unsigned shift = 0; shift < 32; shift += 8) {
|
|
data.push_back(static_cast<unsigned char>(value >> shift));
|
|
}
|
|
}
|
|
const std::array<unsigned char, 8> block = {0x55, 0x77, 0x99, 0, 0, 0, 0, 0};
|
|
data.insert(data.end(), block.begin(), block.end());
|
|
return data;
|
|
}
|
|
|
|
class AssetManagerTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
root_ = MakeTempRoot();
|
|
ASSERT_FALSE(root_.empty());
|
|
setenv("MOCKTAIL_ASSET_ROOT", root_.c_str(), 1);
|
|
unsetenv("MOCKTAIL_ASSET_OVERLAY_DIR");
|
|
const char* backend = std::getenv("MOCKTAIL_GRAPHICS_BACKEND");
|
|
had_backend_ = backend != nullptr;
|
|
previous_backend_ = backend != nullptr ? backend : "";
|
|
unsetenv("MOCKTAIL_GRAPHICS_BACKEND");
|
|
}
|
|
|
|
void TearDown() override {
|
|
unsetenv("MOCKTAIL_ASSET_ROOT");
|
|
unsetenv("MOCKTAIL_ASSET_OVERLAY_DIR");
|
|
if (had_backend_) {
|
|
setenv("MOCKTAIL_GRAPHICS_BACKEND", previous_backend_.c_str(), 1);
|
|
} else {
|
|
unsetenv("MOCKTAIL_GRAPHICS_BACKEND");
|
|
}
|
|
for (const std::string& root : roots_to_remove_) {
|
|
std::filesystem::remove_all(root);
|
|
}
|
|
}
|
|
|
|
std::string MakeTempRoot() {
|
|
char path[] = "/tmp/mocktail-assets-XXXXXX";
|
|
char* dir = mkdtemp(path);
|
|
if (dir == nullptr) {
|
|
return {};
|
|
}
|
|
roots_to_remove_.emplace_back(dir);
|
|
return dir;
|
|
}
|
|
|
|
void WriteAsset(const std::string& name, const char* data) {
|
|
WriteAssetAt(root_, name, data);
|
|
}
|
|
|
|
void WriteAssetAt(const std::string& root, const std::string& name,
|
|
const char* data) {
|
|
std::string path = root + "/" + name;
|
|
std::filesystem::create_directories(
|
|
std::filesystem::path(path).parent_path());
|
|
FILE* file = std::fopen(path.c_str(), "wb");
|
|
ASSERT_NE(file, nullptr);
|
|
ASSERT_EQ(std::fwrite(data, 1, std::strlen(data), file),
|
|
std::strlen(data));
|
|
ASSERT_EQ(std::fclose(file), 0);
|
|
}
|
|
|
|
void WriteBinaryAsset(const std::string& name,
|
|
const std::vector<unsigned char>& data) {
|
|
const std::filesystem::path path = std::filesystem::path(root_) / name;
|
|
std::filesystem::create_directories(path.parent_path());
|
|
std::ofstream file(path, std::ios::binary);
|
|
file.write(reinterpret_cast<const char*>(data.data()), data.size());
|
|
file.close();
|
|
ASSERT_TRUE(file);
|
|
}
|
|
|
|
std::string root_;
|
|
std::vector<std::string> roots_to_remove_;
|
|
bool had_backend_ = false;
|
|
std::string previous_backend_;
|
|
};
|
|
|
|
TEST_F(AssetManagerTest, FromJavaReturnsSingletonManager) {
|
|
EXPECT_NE(AAssetManager_fromJava(nullptr, nullptr), nullptr);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, ConvertsSolidEtc1RgbBlockToOpaqueBc1) {
|
|
const std::array<uint8_t, 8> etc1 = {0x55, 0x77, 0x99, 0, 0, 0, 0, 0};
|
|
std::array<uint8_t, 8> bc1{};
|
|
|
|
libc_shim::ConvertEtc1RgbBlockToBc1(etc1.data(), bc1.data());
|
|
|
|
const uint16_t endpoint0 = static_cast<uint16_t>(bc1[0] | (bc1[1] << 8));
|
|
const uint16_t endpoint1 = static_cast<uint16_t>(bc1[2] | (bc1[3] << 8));
|
|
EXPECT_GT(endpoint0, endpoint1);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, OpenReadSeekAndBufferUseConfiguredAssetRoot) {
|
|
WriteAsset("config.json", "mocktail-assets");
|
|
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
AAsset* asset = AAssetManager_open(manager, "config.json", 0);
|
|
ASSERT_NE(asset, nullptr);
|
|
|
|
EXPECT_EQ(AAsset_getLength(asset), 15);
|
|
char first[5] = {};
|
|
EXPECT_EQ(AAsset_read(asset, first, 4), 4);
|
|
EXPECT_STREQ(first, "mock");
|
|
EXPECT_EQ(AAsset_getRemainingLength(asset), 11);
|
|
|
|
EXPECT_EQ(AAsset_seek(asset, 9, SEEK_SET), 9);
|
|
char second[7] = {};
|
|
EXPECT_EQ(AAsset_read(asset, second, 6), 6);
|
|
EXPECT_STREQ(second, "assets");
|
|
|
|
const auto* buffer = static_cast<const char*>(AAsset_getBuffer(asset));
|
|
ASSERT_NE(buffer, nullptr);
|
|
EXPECT_EQ(std::string(buffer, buffer + AAsset_getLength(asset)),
|
|
"mocktail-assets");
|
|
|
|
AAsset_close(asset);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, OpensExtraContentModelsFromRbxAssetPaths) {
|
|
WriteAsset("ExtraContent/models/UniversalApp/UniversalApp.rbxm", "rbxm");
|
|
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
AAsset* stripped =
|
|
AAssetManager_open(manager, "models/UniversalApp/UniversalApp.rbxm", 0);
|
|
ASSERT_NE(stripped, nullptr);
|
|
EXPECT_EQ(AAsset_getLength(stripped), 4);
|
|
AAsset_close(stripped);
|
|
|
|
AAsset* uri = AAssetManager_open(
|
|
manager, "rbxasset://models/UniversalApp/UniversalApp.rbxm", 0);
|
|
ASSERT_NE(uri, nullptr);
|
|
EXPECT_EQ(AAsset_getLength(uri), 4);
|
|
AAsset_close(uri);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, OpenFileDescriptorReturnsReadableFd) {
|
|
WriteAsset("config.json", "fd-data");
|
|
|
|
AAsset* asset =
|
|
AAssetManager_open(AAssetManager_fromJava(nullptr, nullptr),
|
|
"assets/config.json", 0);
|
|
ASSERT_NE(asset, nullptr);
|
|
|
|
off_t start = -1;
|
|
off_t length = -1;
|
|
int fd = AAsset_openFileDescriptor(asset, &start, &length);
|
|
ASSERT_GE(fd, 0);
|
|
EXPECT_EQ(start, 0);
|
|
EXPECT_EQ(length, 7);
|
|
|
|
char buf[8] = {};
|
|
EXPECT_EQ(read(fd, buf, sizeof(buf) - 1), 7);
|
|
EXPECT_STREQ(buf, "fd-data");
|
|
close(fd);
|
|
AAsset_close(asset);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, TranscodedSkyCannotBypassConversionThroughDescriptor) {
|
|
const auto original = MakeEtc1SkyTexture();
|
|
const std::string name = "assets/android/textures/sky/test.tex";
|
|
WriteBinaryAsset(name, original);
|
|
ASSERT_EQ(setenv("MOCKTAIL_GRAPHICS_BACKEND", "direct-vulkan", 1), 0);
|
|
|
|
AAsset* asset = AAssetManager_open(
|
|
AAssetManager_fromJava(nullptr, nullptr), name.c_str(), 0);
|
|
ASSERT_NE(asset, nullptr);
|
|
ASSERT_EQ(AAsset_getLength(asset), static_cast<off_t>(original.size()));
|
|
const auto* buffer = static_cast<const unsigned char*>(AAsset_getBuffer(asset));
|
|
ASSERT_NE(buffer, nullptr);
|
|
// BC1 format in the KTX header.
|
|
EXPECT_EQ(buffer[28], 0xf0);
|
|
EXPECT_EQ(buffer[29], 0x83);
|
|
|
|
for (auto open_descriptor : {AAsset_openFileDescriptor,
|
|
AAsset_openFileDescriptor64}) {
|
|
off_t start = -1;
|
|
off_t length = -1;
|
|
const int fd = open_descriptor(asset, &start, &length);
|
|
EXPECT_LT(fd, 0);
|
|
if (fd >= 0) close(fd);
|
|
}
|
|
|
|
std::vector<unsigned char> readback(original.size());
|
|
EXPECT_EQ(AAsset_read(asset, readback.data(), readback.size()),
|
|
static_cast<int>(readback.size()));
|
|
EXPECT_EQ(readback, std::vector<unsigned char>(buffer, buffer + readback.size()));
|
|
AAsset_close(asset);
|
|
|
|
std::vector<unsigned char> on_disk(original.size());
|
|
std::ifstream file(root_ + "/" + name, std::ios::binary);
|
|
file.read(reinterpret_cast<char*>(on_disk.data()), on_disk.size());
|
|
EXPECT_EQ(on_disk, original);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, OpenGlSkyKeepsOriginalFileDescriptor) {
|
|
const auto original = MakeEtc1SkyTexture();
|
|
const std::string name = "assets/android/textures/sky/test.tex";
|
|
WriteBinaryAsset(name, original);
|
|
ASSERT_EQ(setenv("MOCKTAIL_GRAPHICS_BACKEND", "opengl", 1), 0);
|
|
|
|
AAsset* asset = AAssetManager_open(
|
|
AAssetManager_fromJava(nullptr, nullptr), name.c_str(), 0);
|
|
ASSERT_NE(asset, nullptr);
|
|
off_t start = -1;
|
|
off_t length = -1;
|
|
const int fd = AAsset_openFileDescriptor64(asset, &start, &length);
|
|
ASSERT_GE(fd, 0);
|
|
EXPECT_EQ(length, static_cast<off_t>(original.size()));
|
|
std::vector<unsigned char> readback(original.size());
|
|
EXPECT_EQ(pread(fd, readback.data(), readback.size(), start),
|
|
static_cast<ssize_t>(readback.size()));
|
|
EXPECT_EQ(readback, original);
|
|
close(fd);
|
|
AAsset_close(asset);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, MissingOrUnsafeAssetsReturnNull) {
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
EXPECT_EQ(AAssetManager_open(manager, "missing.bin", 0), nullptr);
|
|
EXPECT_EQ(AAssetManager_open(manager, "../config.json", 0), nullptr);
|
|
EXPECT_EQ(AAssetManager_open(manager, "/tmp/config.json", 0), nullptr);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, MissingAssetCanBeCreatedAndOpenedLater) {
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
EXPECT_EQ(AAssetManager_open(manager, "created-later.txt", 0), nullptr);
|
|
|
|
WriteAsset("created-later.txt", "now-present");
|
|
AAsset* asset = AAssetManager_open(manager, "created-later.txt", 0);
|
|
ASSERT_NE(asset, nullptr);
|
|
EXPECT_EQ(AAsset_getLength(asset), 11);
|
|
EXPECT_EQ(std::string(static_cast<const char*>(AAsset_getBuffer(asset)),
|
|
static_cast<std::size_t>(AAsset_getLength(asset))),
|
|
"now-present");
|
|
AAsset_close(asset);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, CacheSeparatesRuntimeAssetRootChanges) {
|
|
WriteAsset("same-name.txt", "first-root");
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
AAsset* first = AAssetManager_open(manager, "same-name.txt", 0);
|
|
ASSERT_NE(first, nullptr);
|
|
EXPECT_EQ(std::string(static_cast<const char*>(AAsset_getBuffer(first)),
|
|
static_cast<std::size_t>(AAsset_getLength(first))),
|
|
"first-root");
|
|
AAsset_close(first);
|
|
|
|
const std::string second_root = MakeTempRoot();
|
|
ASSERT_FALSE(second_root.empty());
|
|
WriteAssetAt(second_root, "same-name.txt", "second-root");
|
|
ASSERT_EQ(setenv("MOCKTAIL_ASSET_ROOT", second_root.c_str(), 1), 0);
|
|
|
|
AAsset* second = AAssetManager_open(manager, "same-name.txt", 0);
|
|
ASSERT_NE(second, nullptr);
|
|
EXPECT_EQ(std::string(static_cast<const char*>(AAsset_getBuffer(second)),
|
|
static_cast<std::size_t>(AAsset_getLength(second))),
|
|
"second-root");
|
|
AAsset_close(second);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, OverlayAssetTakesPrecedenceOverRootAsset) {
|
|
WriteAsset("content/textures/Cursors/KeyboardMouse/ArrowCursor.png",
|
|
"stock-arrow");
|
|
const std::string overlay_root = MakeTempRoot();
|
|
ASSERT_FALSE(overlay_root.empty());
|
|
WriteAssetAt(overlay_root,
|
|
"content/textures/Cursors/KeyboardMouse/ArrowCursor.png",
|
|
"custom-overlay-arrow");
|
|
ASSERT_EQ(setenv("MOCKTAIL_ASSET_OVERLAY_DIR", overlay_root.c_str(), 1), 0);
|
|
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
|
|
// 1. Direct content/ path
|
|
AAsset* direct = AAssetManager_open(
|
|
manager, "content/textures/Cursors/KeyboardMouse/ArrowCursor.png", 0);
|
|
ASSERT_NE(direct, nullptr);
|
|
EXPECT_EQ(std::string(static_cast<const char*>(AAsset_getBuffer(direct)),
|
|
static_cast<std::size_t>(AAsset_getLength(direct))),
|
|
"custom-overlay-arrow");
|
|
AAsset_close(direct);
|
|
|
|
// 2. rbxasset:// URI prefix without content/
|
|
AAsset* rbxasset = AAssetManager_open(
|
|
manager, "rbxasset://textures/Cursors/KeyboardMouse/ArrowCursor.png", 0);
|
|
ASSERT_NE(rbxasset, nullptr);
|
|
EXPECT_EQ(std::string(static_cast<const char*>(AAsset_getBuffer(rbxasset)),
|
|
static_cast<std::size_t>(AAsset_getLength(rbxasset))),
|
|
"custom-overlay-arrow");
|
|
AAsset_close(rbxasset);
|
|
|
|
// 3. Bare textures/ prefix
|
|
AAsset* bare = AAssetManager_open(
|
|
manager, "textures/Cursors/KeyboardMouse/ArrowCursor.png", 0);
|
|
ASSERT_NE(bare, nullptr);
|
|
EXPECT_EQ(std::string(static_cast<const char*>(AAsset_getBuffer(bare)),
|
|
static_cast<std::size_t>(AAsset_getLength(bare))),
|
|
"custom-overlay-arrow");
|
|
AAsset_close(bare);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, OverlayOpenFileDescriptorReadsDirectlyFromFile) {
|
|
const std::string overlay_root = MakeTempRoot();
|
|
ASSERT_FALSE(overlay_root.empty());
|
|
WriteAssetAt(overlay_root, "content/custom_shader.bin", "custom-overlay-shader");
|
|
ASSERT_EQ(setenv("MOCKTAIL_ASSET_OVERLAY_DIR", overlay_root.c_str(), 1), 0);
|
|
// Point APK path to non-existent or dummy file to ensure descriptor reads overlay
|
|
ASSERT_EQ(setenv("MOCKTAIL_ASSET_APK_PATH", "/nonexistent/apk/path.apk", 1), 0);
|
|
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
AAsset* asset = AAssetManager_open(manager, "content/custom_shader.bin", 0);
|
|
ASSERT_NE(asset, nullptr);
|
|
|
|
off_t start = -1;
|
|
off_t length = -1;
|
|
int fd = AAsset_openFileDescriptor(asset, &start, &length);
|
|
ASSERT_GE(fd, 0);
|
|
EXPECT_EQ(start, 0);
|
|
EXPECT_EQ(length, 21);
|
|
|
|
char buf[32] = {};
|
|
EXPECT_EQ(pread(fd, buf, 21, start), 21);
|
|
EXPECT_STREQ(buf, "custom-overlay-shader");
|
|
close(fd);
|
|
AAsset_close(asset);
|
|
unsetenv("MOCKTAIL_ASSET_APK_PATH");
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, NonOverlaidAssetFallsBackToRootAsset) {
|
|
WriteAsset("content/sounds/oof.mp3", "stock-sound");
|
|
const std::string overlay_root = MakeTempRoot();
|
|
ASSERT_FALSE(overlay_root.empty());
|
|
WriteAssetAt(overlay_root, "content/textures/unrelated.png", "other-texture");
|
|
ASSERT_EQ(setenv("MOCKTAIL_ASSET_OVERLAY_DIR", overlay_root.c_str(), 1), 0);
|
|
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
AAsset* asset = AAssetManager_open(manager, "sounds/oof.mp3", 0);
|
|
ASSERT_NE(asset, nullptr);
|
|
EXPECT_EQ(std::string(static_cast<const char*>(AAsset_getBuffer(asset)),
|
|
static_cast<std::size_t>(AAsset_getLength(asset))),
|
|
"stock-sound");
|
|
AAsset_close(asset);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, CacheSeparatesOverlayDirectoryChanges) {
|
|
WriteAsset("content/test.txt", "stock");
|
|
const std::string first_overlay = MakeTempRoot();
|
|
ASSERT_FALSE(first_overlay.empty());
|
|
WriteAssetAt(first_overlay, "content/test.txt", "overlay-1");
|
|
ASSERT_EQ(setenv("MOCKTAIL_ASSET_OVERLAY_DIR", first_overlay.c_str(), 1), 0);
|
|
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
AAsset* first = AAssetManager_open(manager, "content/test.txt", 0);
|
|
ASSERT_NE(first, nullptr);
|
|
EXPECT_EQ(std::string(static_cast<const char*>(AAsset_getBuffer(first)),
|
|
static_cast<std::size_t>(AAsset_getLength(first))),
|
|
"overlay-1");
|
|
AAsset_close(first);
|
|
|
|
const std::string second_overlay = MakeTempRoot();
|
|
ASSERT_FALSE(second_overlay.empty());
|
|
WriteAssetAt(second_overlay, "content/test.txt", "overlay-2");
|
|
ASSERT_EQ(setenv("MOCKTAIL_ASSET_OVERLAY_DIR", second_overlay.c_str(), 1), 0);
|
|
|
|
AAsset* second = AAssetManager_open(manager, "content/test.txt", 0);
|
|
ASSERT_NE(second, nullptr);
|
|
EXPECT_EQ(std::string(static_cast<const char*>(AAsset_getBuffer(second)),
|
|
static_cast<std::size_t>(AAsset_getLength(second))),
|
|
"overlay-2");
|
|
AAsset_close(second);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, ResolvesShadersWhenAssetRootPointsToContentOrAssets) {
|
|
WriteAsset("shaders/shaders_vulkan_mobile.pack", "shader-pack-data");
|
|
WriteAsset("content/test.txt", "content-data");
|
|
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
|
|
// 1. Standard asset root pointing to parent assets directory:
|
|
AAsset* asset = AAssetManager_open(manager, "shaders/shaders_vulkan_mobile.pack", 0);
|
|
ASSERT_NE(asset, nullptr);
|
|
off_t start = -1;
|
|
off_t length = -1;
|
|
int fd = AAsset_openFileDescriptor(asset, &start, &length);
|
|
ASSERT_GE(fd, 0);
|
|
EXPECT_EQ(length, 16);
|
|
close(fd);
|
|
AAsset_close(asset);
|
|
|
|
// 2. Relative traversal inside asset folder should normalize safely:
|
|
AAsset* relative_asset = AAssetManager_open(
|
|
manager, "content/../shaders/shaders_vulkan_mobile.pack", 0);
|
|
ASSERT_NE(relative_asset, nullptr);
|
|
AAsset_close(relative_asset);
|
|
|
|
// 3. Fallback when MOCKTAIL_ASSET_ROOT accidentally points to /content directory:
|
|
const std::string content_root = root_ + "/content";
|
|
ASSERT_EQ(setenv("MOCKTAIL_ASSET_ROOT", content_root.c_str(), 1), 0);
|
|
AAsset* fallback_asset = AAssetManager_open(
|
|
manager, "shaders/shaders_vulkan_mobile.pack", 0);
|
|
ASSERT_NE(fallback_asset, nullptr);
|
|
fd = AAsset_openFileDescriptor(fallback_asset, &start, &length);
|
|
ASSERT_GE(fd, 0);
|
|
EXPECT_EQ(length, 16);
|
|
close(fd);
|
|
AAsset_close(fallback_asset);
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, OverlayStillWinsWhenTheRootIsTheContentFolder) {
|
|
WriteAsset("content/textures/ArrowCursor.png", "stock-arrow");
|
|
const std::string overlay_root = MakeTempRoot();
|
|
ASSERT_FALSE(overlay_root.empty());
|
|
WriteAssetAt(overlay_root, "content/textures/ArrowCursor.png",
|
|
"custom-overlay-arrow");
|
|
ASSERT_EQ(setenv("MOCKTAIL_ASSET_OVERLAY_DIR", overlay_root.c_str(), 1), 0);
|
|
const std::string content_root = root_ + "/content";
|
|
ASSERT_EQ(setenv("MOCKTAIL_ASSET_ROOT", content_root.c_str(), 1), 0);
|
|
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
for (const char* name :
|
|
{"content/textures/ArrowCursor.png", "textures/ArrowCursor.png",
|
|
"content/fonts/../textures/ArrowCursor.png"}) {
|
|
AAsset* asset = AAssetManager_open(manager, name, 0);
|
|
ASSERT_NE(asset, nullptr) << name;
|
|
EXPECT_EQ(std::string(static_cast<const char*>(AAsset_getBuffer(asset)),
|
|
static_cast<std::size_t>(AAsset_getLength(asset))),
|
|
"custom-overlay-arrow")
|
|
<< name;
|
|
AAsset_close(asset);
|
|
}
|
|
}
|
|
|
|
TEST_F(AssetManagerTest, DotDotPathsCannotLeaveTheAssetFolders) {
|
|
// The file sits beside the asset root, where only an escape can reach it.
|
|
const std::string outside = root_ + "/../outside-" +
|
|
std::to_string(static_cast<long>(getpid())) +
|
|
".txt";
|
|
{ std::ofstream(outside) << "secret"; }
|
|
const std::string outside_name =
|
|
outside.substr(outside.find_last_of('/') + 1);
|
|
const std::string content_root = root_ + "/content";
|
|
WriteAsset("content/inside.txt", "inside");
|
|
AAssetManager* manager = AAssetManager_fromJava(nullptr, nullptr);
|
|
for (const std::string& root : {root_, content_root}) {
|
|
ASSERT_EQ(setenv("MOCKTAIL_ASSET_ROOT", root.c_str(), 1), 0);
|
|
for (const std::string& name :
|
|
{"../" + outside_name, "content/../../" + outside_name,
|
|
"content/../../../" + outside_name, "a/../../" + outside_name,
|
|
std::string("content/.."), std::string("content/../")}) {
|
|
EXPECT_EQ(AAssetManager_open(manager, name.c_str(), 0), nullptr)
|
|
<< root << " " << name;
|
|
}
|
|
}
|
|
std::remove(outside.c_str());
|
|
}
|
|
|
|
} // namespace
|