1 // Copyright 2019 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <fmt/format.h>
6 #include "common/file_util.h"
7 #include "common/texture.h"
8 #include "core.h"
9 #include "core/custom_tex_cache.h"
10 
11 namespace Core {
12 CustomTexCache::CustomTexCache() = default;
13 
14 CustomTexCache::~CustomTexCache() = default;
15 
IsTextureDumped(u64 hash) const16 bool CustomTexCache::IsTextureDumped(u64 hash) const {
17     return dumped_textures.count(hash);
18 }
19 
SetTextureDumped(const u64 hash)20 void CustomTexCache::SetTextureDumped(const u64 hash) {
21     dumped_textures.insert(hash);
22 }
23 
IsTextureCached(u64 hash) const24 bool CustomTexCache::IsTextureCached(u64 hash) const {
25     return custom_textures.count(hash);
26 }
27 
LookupTexture(u64 hash) const28 const CustomTexInfo& CustomTexCache::LookupTexture(u64 hash) const {
29     return custom_textures.at(hash);
30 }
31 
CacheTexture(u64 hash,const std::vector<u8> & tex,u32 width,u32 height)32 void CustomTexCache::CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height) {
33     custom_textures[hash] = {width, height, tex};
34 }
35 
AddTexturePath(u64 hash,const std::string & path)36 void CustomTexCache::AddTexturePath(u64 hash, const std::string& path) {
37     if (custom_texture_paths.count(hash))
38         LOG_ERROR(Core, "Textures {} and {} conflict!", custom_texture_paths[hash].path, path);
39     else
40         custom_texture_paths[hash] = {path, hash};
41 }
42 
FindCustomTextures(u64 program_id)43 void CustomTexCache::FindCustomTextures(u64 program_id) {
44     // Custom textures are currently stored as
45     // [TitleID]/tex1_[width]x[height]_[64-bit hash]_[format].png
46 
47     const std::string load_path = fmt::format(
48         "{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), program_id);
49 
50     if (FileUtil::Exists(load_path)) {
51         FileUtil::FSTEntry texture_dir;
52         std::vector<FileUtil::FSTEntry> textures;
53         // 64 nested folders should be plenty for most cases
54         FileUtil::ScanDirectoryTree(load_path, texture_dir, 64);
55         FileUtil::GetAllFilesFromNestedEntries(texture_dir, textures);
56 
57         for (const auto& file : textures) {
58             if (file.isDirectory)
59                 continue;
60             if (file.virtualName.substr(0, 5) != "tex1_")
61                 continue;
62 
63             u32 width;
64             u32 height;
65             u64 hash;
66             u32 format; // unused
67             // TODO: more modern way of doing this
68             if (std::sscanf(file.virtualName.c_str(), "tex1_%ux%u_%llX_%u.png", &width, &height,
69                             &hash, &format) == 4) {
70                 AddTexturePath(hash, file.physicalName);
71             }
72         }
73     }
74 }
75 
PreloadTextures(Frontend::ImageInterface & image_interface)76 void CustomTexCache::PreloadTextures(Frontend::ImageInterface& image_interface) {
77     for (const auto& path : custom_texture_paths) {
78         const auto& path_info = path.second;
79         Core::CustomTexInfo tex_info;
80         if (image_interface.DecodePNG(tex_info.tex, tex_info.width, tex_info.height,
81                                       path_info.path)) {
82             // Make sure the texture size is a power of 2
83             std::bitset<32> width_bits(tex_info.width);
84             std::bitset<32> height_bits(tex_info.height);
85             if (width_bits.count() == 1 && height_bits.count() == 1) {
86                 LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path);
87                 Common::FlipRGBA8Texture(tex_info.tex, tex_info.width, tex_info.height);
88                 CacheTexture(path_info.hash, tex_info.tex, tex_info.width, tex_info.height);
89             } else {
90                 LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path);
91             }
92         } else {
93             LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path);
94         }
95     }
96 }
97 
CustomTextureExists(u64 hash) const98 bool CustomTexCache::CustomTextureExists(u64 hash) const {
99     return custom_texture_paths.count(hash);
100 }
101 
LookupTexturePathInfo(u64 hash) const102 const CustomTexPathInfo& CustomTexCache::LookupTexturePathInfo(u64 hash) const {
103     return custom_texture_paths.at(hash);
104 }
105 
IsTexturePathMapEmpty() const106 bool CustomTexCache::IsTexturePathMapEmpty() const {
107     return custom_texture_paths.size() == 0;
108 }
109 } // namespace Core
110