@@ -0,0 +1,167 @@
{
"meta":
{
"title": "Bloom and HUD definitions",
"author": "iwubcode"
},
"groups": [
{
"name": "HUD",
"targets": [
{
"type": "draw_started",
"texture_filename": "tex1_96x96_7b5b0f693c1200ad_5"
},
{
"type": "draw_started",
"texture_filename": "tex1_40x48_b510b4434b7de70c_5"
},
{
"type": "draw_started",
"texture_filename": "tex1_96x96_633c30835459df0f_5"
},
{
"type": "draw_started",
"texture_filename": "tex1_24x68_715518a00c14e148_5"
},
{
"type": "draw_started",
"texture_filename": "tex1_32x80_f310048c1139815d_14"
},
{
"type": "draw_started",
"texture_filename": "tex1_12x16_1e9016c61dfffb7a_14"
},
{
"type": "draw_started",
"texture_filename": "tex1_12x16_459c7d7576547909_14"
},
{
"type": "draw_started",
"texture_filename": "tex1_12x16_d1b77f0000ff337a_14"
},
{
"type": "draw_started",
"texture_filename": "tex1_20x16_798aee4dc7001432_14"
},
{
"type": "draw_started",
"texture_filename": "tex1_108x122_911fa08f1554752c_5"
},
{
"type": "draw_started",
"texture_filename": "tex1_64x64_96894941f5454ead_3"
},
{
"type": "draw_started",
"texture_filename": "tex1_8x8_0017d44adaf2291b_14"
},
{
"type": "draw_started",
"texture_filename": "tex1_10x8_7a6a869d5553c4a0_14"
},
{
"type": "draw_started",
"texture_filename": "tex1_16x16_49c191eaf4314e9d_14"
},
{
"type": "draw_started",
"texture_filename": "tex1_18x16_472b403cdcfc31a3_3"
},
{
"type": "draw_started",
"texture_filename": "tex1_30x26_629956a45175e53a_14"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_96x96_7b5b0f693c1200ad_5"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_40x48_b510b4434b7de70c_5"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_96x96_633c30835459df0f_5"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_24x68_715518a00c14e148_5"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_32x80_f310048c1139815d_14"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_12x16_1e9016c61dfffb7a_14"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_12x16_459c7d7576547909_14"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_12x16_d1b77f0000ff337a_14"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_20x16_798aee4dc7001432_14"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_108x122_911fa08f1554752c_5"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_64x64_96894941f5454ead_3"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_8x8_0017d44adaf2291b_14"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_10x8_7a6a869d5553c4a0_14"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_16x16_49c191eaf4314e9d_14"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_18x16_472b403cdcfc31a3_3"
},
{
"type": "projection",
"value": "2d",
"texture_filename": "tex1_30x26_629956a45175e53a_14"
}
]
},
{
"name": "Bloom",
"targets": [
{
"type": "efb",
"texture_filename": "efb1_n1_320x240_6"
}
]
}
]
}
Empty file.
@@ -0,0 +1,19 @@
{
"meta":
{
"title": "Bloom Texture Definitions",
"author": "iwubcode"
},
"groups":
[
{
"name": "Bloom",
"targets": [
{
"type": "efb",
"texture_filename": "efb1_n49_152x114_6"
}
]
}
]
}
Empty file.
@@ -0,0 +1,27 @@
{
"meta":
{
"title": "Bloom Texture Definitions",
"author": "iwubcode"
},
"groups":
[
{
"name": "Bloom",
"targets": [
{
"type": "efb",
"texture_filename": "efb1_n3_80x56_6"
},
{
"type": "efb",
"texture_filename": "efb1_n2_160x112_6"
},
{
"type": "efb",
"texture_filename": "efb1_n6_320x224_6"
}
]
}
]
}
Empty file.
@@ -0,0 +1,19 @@
{
"meta":
{
"title": "Bloom Texture Definitions",
"author": "iwubcode"
},
"groups":
[
{
"name": "Bloom",
"targets": [
{
"type": "efb",
"texture_filename": "efb1_n000019_128x128_4"
}
]
}
]
}
Empty file.
@@ -0,0 +1,19 @@
{
"meta":
{
"title": "Bloom Texture Definitions",
"author": "iwubcode"
},
"groups":
[
{
"name": "Bloom",
"targets": [
{
"type": "efb",
"texture_filename": "efb1_n51_320x240_6"
}
]
}
]
}
Empty file.
@@ -0,0 +1,103 @@
{
"meta":
{
"title": "Bloom and HUD Texture Definitions",
"author": "iwubcode"
},
"groups":
[
{
"name": "HUD",
"targets": [
{
"type": "draw_started",
"pretty_name": "hp_rp",
"texture_filename": "tex1_200x64_29bf40765535b389_fb4403f0539ecfc6_9"
},
{
"type": "projection",
"value": "2d",
"pretty_name": "hp_rp",
"texture_filename": "tex1_200x64_29bf40765535b389_fb4403f0539ecfc6_9"
},
{
"type": "draw_started",
"pretty_name": "hp_gradient",
"texture_filename": "tex1_96x8_491977b196c249d8_1feafe410943bfac_8"
},
{
"type": "projection",
"value": "2d",
"pretty_name": "hp_gradient",
"texture_filename": "tex1_96x8_491977b196c249d8_1feafe410943bfac_8"
},
{
"type": "draw_started",
"pretty_name": "rp_gradient",
"texture_filename": "tex1_96x8_cdcb5c030686767c_2c5a8138bfca228c_8"
},
{
"type": "projection",
"value": "2d",
"pretty_name": "rp_gradient",
"texture_filename": "tex1_96x8_cdcb5c030686767c_2c5a8138bfca228c_8"
},
{
"type": "draw_started",
"pretty_name": "spring_season",
"texture_filename": "tex1_256x40_30d99f26895bc811_02e626cce31a83ae_9"
},
{
"type": "projection",
"value": "2d",
"pretty_name": "spring_season",
"texture_filename": "tex1_256x40_30d99f26895bc811_02e626cce31a83ae_9"
},
{
"type": "draw_started",
"pretty_name": "quick_pick_box",
"texture_filename": "tex1_128x128_b87c102764a80c67_0488ebdbd87cfc9d_9"
},
{
"type": "projection",
"value": "2d",
"pretty_name": "quick_pick_box",
"texture_filename": "tex1_128x128_b87c102764a80c67_0488ebdbd87cfc9d_9"
},
{
"type": "draw_started",
"pretty_name": "face",
"texture_filename": "tex1_48x48_92405f9277895cd2_914f5a4762aa04ae_9"
},
{
"type": "projection",
"value": "2d",
"pretty_name": "face",
"texture_filename": "tex1_48x48_92405f9277895cd2_914f5a4762aa04ae_9"
},
{
"type": "draw_started",
"pretty_name": "sunny_icon",
"texture_filename": "tex1_24x24_3791555ba7e8186f_e82e2316ceba262d_9"
},
{
"type": "projection",
"value": "2d",
"pretty_name": "sunny_icon",
"texture_filename": "tex1_24x24_3791555ba7e8186f_e82e2316ceba262d_9"
},
{
"type": "draw_started",
"pretty_name": "text",
"texture_filename": "tex1_256x256_83aa16840fa69ffb_0"
},
{
"type": "projection",
"value": "2d",
"pretty_name": "text",
"texture_filename": "tex1_256x256_83aa16840fa69ffb_0"
}
]
}
]
}
Empty file.
@@ -0,0 +1,27 @@
{
"meta":
{
"title": "Bloom Texture Definitions",
"author": "iwubcode"
},
"groups":
[
{
"name": "Bloom",
"targets": [
{
"type": "efb",
"texture_filename": "efb1_n000031_80x57_4"
},
{
"type": "efb",
"texture_filename": "efb1_n000033_160x114_4"
},
{
"type": "efb",
"texture_filename": "efb1_n000038_320x228_4"
}
]
}
]
}
Empty file.
@@ -0,0 +1,31 @@
{
"meta":
{
"title": "Bloom Texture Definitions",
"author": "iwubcode"
},
"groups":
[
{
"name": "Bloom",
"targets": [
{
"type": "efb",
"texture_filename": "efb1_n001461_40x28_1"
},
{
"type": "efb",
"texture_filename": "efb1_n001460_80x56_1"
},
{
"type": "efb",
"texture_filename": "efb1_n001459_160x112_1"
},
{
"type": "efb",
"texture_filename": "efb1_n001458_320x224_1"
}
]
}
]
}
Empty file.
@@ -0,0 +1,31 @@
{
"meta":
{
"title": "Bloom Texture Definitions",
"author": "iwubcode"
},
"groups":
[
{
"name": "Bloom",
"targets": [
{
"type": "efb",
"texture_filename": "efb1_n000022_40x28_6"
},
{
"type": "efb",
"texture_filename": "efb1_n000021_80x56_6"
},
{
"type": "efb",
"texture_filename": "efb1_n000020_160x112_6"
},
{
"type": "efb",
"texture_filename": "efb1_n000025_320x224_6"
}
]
}
]
}
Empty file.
@@ -0,0 +1,23 @@
{
"meta":
{
"title": "Bloom Texture Definitions",
"author": "iwubcode"
},
"groups":
[
{
"name": "Bloom",
"targets": [
{
"type": "efb",
"texture_filename": "efb1_n55_80x57_6"
},
{
"type": "efb",
"texture_filename": "efb1_n54_160x114_6"
}
]
}
]
}
Empty file.
@@ -0,0 +1,31 @@
{
"meta":
{
"title": "Bloom Texture Definitions",
"author": "iwubcode"
},
"groups":
[
{
"name": "Bloom",
"targets": [
{
"type": "efb",
"texture_filename": "efb1_n15_20x16_4"
},
{
"type": "efb",
"texture_filename": "efb1_n9_40x30_4"
},
{
"type": "efb",
"texture_filename": "efb1_n7_80x58_4"
},
{
"type": "efb",
"texture_filename": "efb1_n1_320x228_4"
}
]
}
]
}
@@ -72,6 +72,7 @@
#define BACKUP_DIR "Backup"
#define RESOURCEPACK_DIR "ResourcePacks"
#define DYNAMICINPUT_DIR "DynamicInputTextures"
#define GRAPHICSMOD_DIR "GraphicMods"

// This one is only used to remove it if it was present
#define SHADERCACHE_LEGACY_DIR "ShaderCache"
@@ -137,3 +138,6 @@
// Subdirs in Sys
#define GC_SYS_DIR "GC"
#define WII_SYS_DIR "Wii"

// Subdirs in Config
#define GRAPHICSMOD_CONFIG_DIR "GraphicMods"
@@ -967,6 +967,7 @@ static void RebuildUserDirectories(unsigned int dir_index)
s_user_paths[D_BACKUP_IDX] = s_user_paths[D_USER_IDX] + BACKUP_DIR DIR_SEP;
s_user_paths[D_RESOURCEPACK_IDX] = s_user_paths[D_USER_IDX] + RESOURCEPACK_DIR DIR_SEP;
s_user_paths[D_DYNAMICINPUT_IDX] = s_user_paths[D_LOAD_IDX] + DYNAMICINPUT_DIR DIR_SEP;
s_user_paths[D_GRAPHICSMOD_IDX] = s_user_paths[D_LOAD_IDX] + GRAPHICSMOD_DIR DIR_SEP;
s_user_paths[F_DOLPHINCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DOLPHIN_CONFIG;
s_user_paths[F_GCPADCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + GCPAD_CONFIG;
s_user_paths[F_WIIPADCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + WIIPAD_CONFIG;
@@ -1045,6 +1046,7 @@ static void RebuildUserDirectories(unsigned int dir_index)
s_user_paths[D_HIRESTEXTURES_IDX] = s_user_paths[D_LOAD_IDX] + HIRES_TEXTURES_DIR DIR_SEP;
s_user_paths[D_RIIVOLUTION_IDX] = s_user_paths[D_LOAD_IDX] + RIIVOLUTION_DIR DIR_SEP;
s_user_paths[D_DYNAMICINPUT_IDX] = s_user_paths[D_LOAD_IDX] + DYNAMICINPUT_DIR DIR_SEP;
s_user_paths[D_GRAPHICSMOD_IDX] = s_user_paths[D_LOAD_IDX] + GRAPHICSMOD_DIR DIR_SEP;
break;
}
}
@@ -60,6 +60,7 @@ enum
D_BACKUP_IDX,
D_RESOURCEPACK_IDX,
D_DYNAMICINPUT_IDX,
D_GRAPHICSMOD_IDX,
D_GBAUSER_IDX,
D_GBASAVES_IDX,
FIRST_FILE_USER_PATH_IDX,
@@ -101,6 +101,8 @@ const Info<int> GFX_SW_DRAW_END{{System::GFX, "Settings", "SWDrawEnd"}, 100000};

const Info<bool> GFX_PREFER_GLES{{System::GFX, "Settings", "PreferGLES"}, false};

const Info<bool> GFX_MODS_ENABLE{{System::GFX, "Settings", "EnableMods"}, false};

// Graphics.Enhancements

const Info<bool> GFX_ENHANCE_FORCE_FILTERING{{System::GFX, "Enhancements", "ForceFiltering"},
@@ -82,6 +82,8 @@ extern const Info<int> GFX_SW_DRAW_END;

extern const Info<bool> GFX_PREFER_GLES;

extern const Info<bool> GFX_MODS_ENABLE;

// Graphics.Enhancements

extern const Info<bool> GFX_ENHANCE_FORCE_FILTERING;
@@ -631,6 +631,21 @@
<ClInclude Include="VideoCommon\FreeLookCamera.h" />
<ClInclude Include="VideoCommon\GeometryShaderGen.h" />
<ClInclude Include="VideoCommon\GeometryShaderManager.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Config\GraphicsMod.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Config\GraphicsModFeature.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Config\GraphicsModGroup.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Config\GraphicsTarget.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Config\GraphicsTargetGroup.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Constants.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\MoveAction.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\PrintAction.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\ScaleAction.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\SkipAction.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\FBInfo.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModAction.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModActionFactory.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModGroup.h" />
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModManager.h" />
<ClInclude Include="VideoCommon\GXPipelineTypes.h" />
<ClInclude Include="VideoCommon\HiresTextures.h" />
<ClInclude Include="VideoCommon\ImageWrite.h" />
@@ -1209,6 +1224,18 @@
<ClCompile Include="VideoCommon\FreeLookCamera.cpp" />
<ClCompile Include="VideoCommon\GeometryShaderGen.cpp" />
<ClCompile Include="VideoCommon\GeometryShaderManager.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Config\GraphicsMod.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Config\GraphicsModFeature.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Config\GraphicsModGroup.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Config\GraphicsTarget.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Config\GraphicsTargetGroup.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\MoveAction.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\PrintAction.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\ScaleAction.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\SkipAction.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\FBInfo.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModActionFactory.cpp" />
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModManager.cpp" />
<ClCompile Include="VideoCommon\HiresTextures_DDSLoader.cpp" />
<ClCompile Include="VideoCommon\HiresTextures.cpp" />
<ClCompile Include="VideoCommon\IndexGenerator.cpp" />
@@ -129,6 +129,10 @@ add_executable(dolphin-emu
Config/Graphics/PostProcessingConfigWindow.h
Config/Graphics/SoftwareRendererWidget.cpp
Config/Graphics/SoftwareRendererWidget.h
Config/GraphicsModListWidget.cpp
Config/GraphicsModListWidget.h
Config/GraphicsModWarningWidget.cpp
Config/GraphicsModWarningWidget.h
Config/InfoWidget.cpp
Config/InfoWidget.h
Config/LogConfigWidget.cpp
@@ -20,6 +20,7 @@
#include "DolphinQt/Config/Graphics/GraphicsInteger.h"
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
#include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h"
#include "DolphinQt/QtUtils/SignalBlocking.h"
#include "DolphinQt/Settings.h"

#include "VideoCommon/VideoConfig.h"
@@ -72,13 +73,15 @@ void AdvancedWidget::CreateWidgets()
m_dump_xfb_target = new GraphicsBool(tr("Dump XFB Target"), Config::GFX_DUMP_XFB_TARGET);
m_disable_vram_copies =
new GraphicsBool(tr("Disable EFB VRAM Copies"), Config::GFX_HACK_DISABLE_COPY_TO_VRAM);
m_enable_graphics_mods = new ToolTipCheckBox(tr("Enable Graphics Mods"));

utility_layout->addWidget(m_load_custom_textures, 0, 0);
utility_layout->addWidget(m_prefetch_custom_textures, 0, 1);

utility_layout->addWidget(m_disable_vram_copies, 1, 0);
utility_layout->addWidget(m_enable_graphics_mods, 1, 1);

utility_layout->addWidget(m_dump_efb_target, 1, 1);
utility_layout->addWidget(m_dump_efb_target, 2, 0);
utility_layout->addWidget(m_dump_xfb_target, 2, 1);

// Texture dumping
@@ -165,6 +168,7 @@ void AdvancedWidget::ConnectWidgets()
connect(m_dump_use_ffv1, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings);
connect(m_enable_prog_scan, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings);
connect(m_dump_textures, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings);
connect(m_enable_graphics_mods, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings);
}

void AdvancedWidget::LoadSettings()
@@ -175,6 +179,8 @@ void AdvancedWidget::LoadSettings()
m_enable_prog_scan->setChecked(Config::Get(Config::SYSCONF_PROGRESSIVE_SCAN));
m_dump_mip_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES));
m_dump_base_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES));

SignalBlocking(m_enable_graphics_mods)->setChecked(Settings::Instance().GetGraphicModsEnabled());
}

void AdvancedWidget::SaveSettings()
@@ -185,6 +191,7 @@ void AdvancedWidget::SaveSettings()
Config::SetBase(Config::SYSCONF_PROGRESSIVE_SCAN, m_enable_prog_scan->isChecked());
m_dump_mip_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES));
m_dump_base_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES));
Settings::Instance().SetGraphicModsEnabled(m_enable_graphics_mods->isChecked());
}

void AdvancedWidget::OnBackendChanged()
@@ -245,6 +252,9 @@ void AdvancedWidget::AddDescriptions()
QT_TR_NOOP("Disables the VRAM copy of the EFB, forcing a round-trip to RAM. Inhibits all "
"upscaling.<br><br><dolphin_emphasis>If unsure, leave this "
"unchecked.</dolphin_emphasis>");
static const char TR_LOAD_GRAPHICS_MODS_DESCRIPTION[] =
QT_TR_NOOP("Loads graphics mods from User/Load/GraphicsMods/.<br><br><dolphin_emphasis>If "
"unsure, leave this unchecked.</dolphin_emphasis>");
static const char TR_INTERNAL_RESOLUTION_FRAME_DUMPING_DESCRIPTION[] = QT_TR_NOOP(
"Creates frame dumps and screenshots at the internal resolution of the renderer, rather than "
"the size of the window it is displayed within.<br><br>If the aspect ratio is widescreen, "
@@ -316,6 +326,7 @@ void AdvancedWidget::AddDescriptions()
m_dump_efb_target->SetDescription(tr(TR_DUMP_EFB_DESCRIPTION));
m_dump_xfb_target->SetDescription(tr(TR_DUMP_XFB_DESCRIPTION));
m_disable_vram_copies->SetDescription(tr(TR_DISABLE_VRAM_COPIES_DESCRIPTION));
m_enable_graphics_mods->SetDescription(tr(TR_LOAD_GRAPHICS_MODS_DESCRIPTION));
m_use_fullres_framedumps->SetDescription(tr(TR_INTERNAL_RESOLUTION_FRAME_DUMPING_DESCRIPTION));
#ifdef HAVE_FFMPEG
m_dump_use_ffv1->SetDescription(tr(TR_USE_FFV1_DESCRIPTION));
@@ -42,6 +42,7 @@ class AdvancedWidget final : public GraphicsWidget
GraphicsBool* m_dump_xfb_target;
GraphicsBool* m_disable_vram_copies;
GraphicsBool* m_load_custom_textures;
ToolTipCheckBox* m_enable_graphics_mods;

// Texture dumping
GraphicsBool* m_dump_textures;
@@ -0,0 +1,279 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "DolphinQt/Config/GraphicsModListWidget.h"

#include <QCheckBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>

#include <set>

#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "DolphinQt/Config/GraphicsModWarningWidget.h"
#include "DolphinQt/Settings.h"
#include "UICommon/GameFile.h"
#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
#include "VideoCommon/VideoConfig.h"

GraphicsModListWidget::GraphicsModListWidget(const UICommon::GameFile& game)
: m_game_id(game.GetGameID()), m_mod_group(m_game_id)
{
CalculateGameRunning(Core::GetState());
if (m_loaded_game_is_running && g_Config.graphics_mod_config)
{
m_mod_group.SetChangeCount(g_Config.graphics_mod_config->GetChangeCount());
}
CreateWidgets();
ConnectWidgets();

RefreshModList();
OnModChanged(std::nullopt);
}

GraphicsModListWidget::~GraphicsModListWidget()
{
if (m_needs_save)
{
m_mod_group.Save();
}
}

void GraphicsModListWidget::CreateWidgets()
{
auto* main_v_layout = new QVBoxLayout(this);

auto* main_layout = new QHBoxLayout;

auto* left_v_layout = new QVBoxLayout;

m_mod_list = new QListWidget;
m_mod_list->setSortingEnabled(false);
m_mod_list->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectItems);
m_mod_list->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
m_mod_list->setSelectionRectVisible(true);
m_mod_list->setDragDropMode(QAbstractItemView::InternalMove);

m_refresh = new QPushButton(tr("&Refresh List"));
QHBoxLayout* hlayout = new QHBoxLayout;
hlayout->addStretch();
hlayout->addWidget(m_refresh);

left_v_layout->addWidget(m_mod_list);
left_v_layout->addLayout(hlayout);

auto* right_v_layout = new QVBoxLayout;

m_selected_mod_name = new QLabel();
right_v_layout->addWidget(m_selected_mod_name);

m_mod_meta_layout = new QVBoxLayout;
right_v_layout->addLayout(m_mod_meta_layout);
right_v_layout->addStretch();

main_layout->addLayout(left_v_layout);
main_layout->addLayout(right_v_layout, 1);

m_warning = new GraphicsModWarningWidget(this);
main_v_layout->addWidget(m_warning);
main_v_layout->addLayout(main_layout);

setLayout(main_v_layout);
}

void GraphicsModListWidget::ConnectWidgets()
{
connect(m_warning, &GraphicsModWarningWidget::GraphicsModEnableSettings, this,
&GraphicsModListWidget::OpenGraphicsSettings);

connect(m_mod_list, &QListWidget::itemSelectionChanged, this,
&GraphicsModListWidget::ModSelectionChanged);

connect(m_mod_list, &QListWidget::itemChanged, this, &GraphicsModListWidget::ModItemChanged);

connect(m_mod_list->model(), &QAbstractItemModel::rowsMoved, this,
&GraphicsModListWidget::SaveModList);

connect(m_refresh, &QPushButton::clicked, this, &GraphicsModListWidget::RefreshModList);

connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
&GraphicsModListWidget::CalculateGameRunning);
}

void GraphicsModListWidget::RefreshModList()
{
m_mod_list->setCurrentItem(nullptr);
m_mod_list->clear();

m_mod_group = GraphicsModGroupConfig(m_game_id);
m_mod_group.Load();

std::set<std::string> groups;

for (const auto& mod : m_mod_group.GetMods())
{
if (mod.m_groups.empty())
continue;

for (const auto& group : mod.m_groups)
{
groups.insert(group.m_name);
}
}

for (const auto& mod : m_mod_group.GetMods())
{
// Group only mods shouldn't be shown
if (mod.m_features.empty())
continue;

// If the group doesn't exist in the available mod's features, skip
if (std::none_of(mod.m_features.begin(), mod.m_features.end(),
[&groups](const GraphicsModFeatureConfig& feature) {
return groups.count(feature.m_group) == 1;
}))
{
continue;
}

QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(mod.m_title));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setData(Qt::UserRole, QString::fromStdString(mod.GetAbsolutePath()));
item->setCheckState(mod.m_enabled ? Qt::Checked : Qt::Unchecked);

m_mod_list->addItem(item);
}
}

void GraphicsModListWidget::ModSelectionChanged()
{
if (m_mod_list->currentItem() == nullptr)
return;
if (m_mod_list->count() == 0)
return;
const auto absolute_path = m_mod_list->currentItem()->data(Qt::UserRole).toString().toStdString();
OnModChanged(absolute_path);
}

void GraphicsModListWidget::ModItemChanged(QListWidgetItem* item)
{
const auto absolute_path = item->data(Qt::UserRole).toString();
GraphicsModConfig* mod = m_mod_group.GetMod(absolute_path.toStdString());
if (!mod)
return;

const bool was_enabled = mod->m_enabled;
const bool should_enable = item->checkState() == Qt::Checked;
mod->m_enabled = should_enable;
if (was_enabled == should_enable)
return;

m_mod_group.SetChangeCount(m_mod_group.GetChangeCount() + 1);
if (m_loaded_game_is_running)
{
g_Config.graphics_mod_config = m_mod_group;
}
m_needs_save = true;
}

void GraphicsModListWidget::OnModChanged(std::optional<std::string> absolute_path)
{
ClearLayoutRecursively(m_mod_meta_layout);

adjustSize();

if (!absolute_path)
{
m_selected_mod_name->setText(QStringLiteral("No graphics mod selected"));
m_selected_mod_name->setAlignment(Qt::AlignCenter);
return;
}

GraphicsModConfig* mod = m_mod_group.GetMod(*absolute_path);
if (!mod)
return;

m_selected_mod_name->setText(QString::fromStdString(mod->m_title));
m_selected_mod_name->setAlignment(Qt::AlignLeft);
QFont font = m_selected_mod_name->font();
font.setWeight(QFont::Bold);
m_selected_mod_name->setFont(font);

if (!mod->m_author.empty())
{
auto* author_label = new QLabel(tr("By: ") + QString::fromStdString(mod->m_author));
m_mod_meta_layout->addWidget(author_label);
}

if (!mod->m_description.empty())
{
auto* description_label =
new QLabel(tr("Description: ") + QString::fromStdString(mod->m_description));
m_mod_meta_layout->addWidget(description_label);
}
}

void GraphicsModListWidget::SaveModList()
{
for (int i = 0; i < m_mod_list->count(); i++)
{
const auto absolute_path = m_mod_list->model()
->data(m_mod_list->model()->index(i, 0), Qt::UserRole)
.toString()
.toStdString();
m_mod_group.GetMod(absolute_path)->m_weight = i;
}

if (m_loaded_game_is_running)
{
g_Config.graphics_mod_config = m_mod_group;
}
m_needs_save = true;
}

void GraphicsModListWidget::ClearLayoutRecursively(QLayout* layout)
{
while (QLayoutItem* child = layout->takeAt(0))
{
if (child == nullptr)
continue;

if (child->widget())
{
layout->removeWidget(child->widget());
delete child->widget();
}
else if (child->layout())
{
ClearLayoutRecursively(child->layout());
layout->removeItem(child);
}
else
{
layout->removeItem(child);
}
delete child;
}
}

void GraphicsModListWidget::SaveToDisk()
{
m_needs_save = false;
m_mod_group.Save();
}

const GraphicsModGroupConfig& GraphicsModListWidget::GetGraphicsModConfig() const
{
return m_mod_group;
}

void GraphicsModListWidget::CalculateGameRunning(Core::State state)
{
m_loaded_game_is_running =
state == Core::State::Running ? m_game_id == SConfig::GetInstance().GetGameID() : false;
}
@@ -0,0 +1,75 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <optional>
#include <string>

#include <QWidget>

#include "Common/CommonTypes.h"
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"

class GraphicsModWarningWidget;
class QHBoxLayout;
class QLabel;
class QListWidget;
class QListWidgetItem;
class QModelIndex;
class QPushButton;
class QVBoxLayout;

namespace Core
{
enum class State;
}

namespace UICommon
{
class GameFile;
}

class GraphicsModListWidget : public QWidget
{
Q_OBJECT
public:
explicit GraphicsModListWidget(const UICommon::GameFile& game);
~GraphicsModListWidget();

void SaveToDisk();

const GraphicsModGroupConfig& GetGraphicsModConfig() const;

signals:
void OpenGraphicsSettings();

private:
void CreateWidgets();
void ConnectWidgets();

void RefreshModList();
void ModSelectionChanged();
void ModItemChanged(QListWidgetItem* item);

void OnModChanged(std::optional<std::string> absolute_path);

void SaveModList();

void ClearLayoutRecursively(QLayout* layout);

void CalculateGameRunning(Core::State state);
bool m_loaded_game_is_running = false;
bool m_needs_save = false;

QListWidget* m_mod_list;

QLabel* m_selected_mod_name;
QVBoxLayout* m_mod_meta_layout;

QPushButton* m_refresh;
GraphicsModWarningWidget* m_warning;

std::string m_game_id;
GraphicsModGroupConfig m_mod_group;
};
@@ -0,0 +1,70 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "DolphinQt/Config/GraphicsModWarningWidget.h"

#include <QHBoxLayout>
#include <QLabel>
#include <QPixmap>
#include <QPushButton>
#include <QStyle>

#include "DolphinQt/Settings.h"

GraphicsModWarningWidget::GraphicsModWarningWidget(QWidget* parent) : QWidget(parent)
{
CreateWidgets();
ConnectWidgets();

connect(&Settings::Instance(), &Settings::EnableGfxModsChanged, this,
&GraphicsModWarningWidget::Update);
Update();
}

void GraphicsModWarningWidget::CreateWidgets()
{
auto* icon = new QLabel;

const auto size = 1.5 * QFontMetrics(font()).height();

QPixmap warning_icon = style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(size, size);

icon->setPixmap(warning_icon);

m_text = new QLabel();
m_config_button = new QPushButton(tr("Configure Dolphin"));

m_config_button->setHidden(true);

auto* layout = new QHBoxLayout;

layout->addWidget(icon);
layout->addWidget(m_text, 1);
layout->addWidget(m_config_button);

layout->setContentsMargins(0, 0, 0, 0);

setLayout(layout);
}

void GraphicsModWarningWidget::Update()
{
bool hide_widget = true;
bool hide_config_button = true;

if (!Settings::Instance().GetGraphicModsEnabled())
{
hide_widget = false;
hide_config_button = false;
m_text->setText(tr("Graphics mods are currently disabled."));
}

setHidden(hide_widget);
m_config_button->setHidden(hide_config_button);
}

void GraphicsModWarningWidget::ConnectWidgets()
{
connect(m_config_button, &QPushButton::clicked, this,
&GraphicsModWarningWidget::GraphicsModEnableSettings);
}
@@ -0,0 +1,28 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <QWidget>

class QLabel;
class QPushButton;

class GraphicsModWarningWidget final : public QWidget
{
Q_OBJECT
public:
explicit GraphicsModWarningWidget(QWidget* parent);

signals:
void GraphicsModEnableSettings();

private:
void CreateWidgets();
void ConnectWidgets();

void Update();

QLabel* m_text;
QPushButton* m_config_button;
};
@@ -17,12 +17,14 @@
#include "DolphinQt/Config/FilesystemWidget.h"
#include "DolphinQt/Config/GameConfigWidget.h"
#include "DolphinQt/Config/GeckoCodeWidget.h"
#include "DolphinQt/Config/GraphicsModListWidget.h"
#include "DolphinQt/Config/InfoWidget.h"
#include "DolphinQt/Config/PatchesWidget.h"
#include "DolphinQt/Config/VerifyWidget.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"

#include "UICommon/GameFile.h"
#include "VideoCommon/VideoConfig.h"

PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& game)
: QDialog(parent)
@@ -43,12 +45,16 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& ga
new GeckoCodeWidget(game.GetGameID(), game.GetGameTDBID(), game.GetRevision());
PatchesWidget* patches = new PatchesWidget(game);
GameConfigWidget* game_config = new GameConfigWidget(game);
GraphicsModListWidget* graphics_mod_list = new GraphicsModListWidget(game);

connect(gecko, &GeckoCodeWidget::OpenGeneralSettings, this,
&PropertiesDialog::OpenGeneralSettings);

connect(ar, &ARCodeWidget::OpenGeneralSettings, this, &PropertiesDialog::OpenGeneralSettings);

connect(graphics_mod_list, &GraphicsModListWidget::OpenGraphicsSettings, this,
&PropertiesDialog::OpenGraphicsSettings);

const int padding_width = 120;
const int padding_height = 100;
tab_widget->addTab(GetWrappedWidget(game_config, this, padding_width, padding_height),
@@ -57,6 +63,8 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& ga
tab_widget->addTab(GetWrappedWidget(ar, this, padding_width, padding_height), tr("AR Codes"));
tab_widget->addTab(GetWrappedWidget(gecko, this, padding_width, padding_height),
tr("Gecko Codes"));
tab_widget->addTab(GetWrappedWidget(graphics_mod_list, this, padding_width, padding_height),
tr("Graphics Mods"));
tab_widget->addTab(GetWrappedWidget(info, this, padding_width, padding_height), tr("Info"));

if (game.GetPlatform() != DiscIO::Platform::ELFOrDOL)
@@ -82,6 +90,8 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& ga
QDialogButtonBox* close_box = new QDialogButtonBox(QDialogButtonBox::Close);

connect(close_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(close_box, &QDialogButtonBox::rejected, graphics_mod_list,
&GraphicsModListWidget::SaveToDisk);

layout->addWidget(close_box);

@@ -18,4 +18,5 @@ class PropertiesDialog final : public QDialog

signals:
void OpenGeneralSettings();
void OpenGraphicsSettings();
};
@@ -76,6 +76,8 @@
<ClCompile Include="Config\Graphics\HacksWidget.cpp" />
<ClCompile Include="Config\Graphics\PostProcessingConfigWindow.cpp" />
<ClCompile Include="Config\Graphics\SoftwareRendererWidget.cpp" />
<ClCompile Include="Config\GraphicsModListWidget.cpp" />
<ClCompile Include="Config\GraphicsModWarningWidget.cpp" />
<ClCompile Include="Config\InfoWidget.cpp" />
<ClCompile Include="Config\LogConfigWidget.cpp" />
<ClCompile Include="Config\LogWidget.cpp" />
@@ -270,6 +272,8 @@
<QtMoc Include="Config\Graphics\HacksWidget.h" />
<QtMoc Include="Config\Graphics\PostProcessingConfigWindow.h" />
<QtMoc Include="Config\Graphics\SoftwareRendererWidget.h" />
<QtMoc Include="Config\GraphicsModListWidget.h" />
<QtMoc Include="Config\GraphicsModWarningWidget.h" />
<QtMoc Include="Config\InfoWidget.h" />
<QtMoc Include="Config\LogConfigWidget.h" />
<QtMoc Include="Config\LogWidget.h" />
@@ -540,6 +540,8 @@ void GameList::OpenProperties()
properties->setAttribute(Qt::WA_DeleteOnClose, true);

connect(properties, &PropertiesDialog::OpenGeneralSettings, this, &GameList::OpenGeneralSettings);
connect(properties, &PropertiesDialog::OpenGraphicsSettings, this,
&GameList::OpenGraphicsSettings);

properties->show();
}
@@ -55,6 +55,7 @@ class GameList final : public QStackedWidget
void NetPlayHost(const UICommon::GameFile& game);
void SelectionChanged(std::shared_ptr<const UICommon::GameFile> game_file);
void OpenGeneralSettings();
void OpenGraphicsSettings();

private:
void ShowHeaderContextMenu(const QPoint& pos);
@@ -672,6 +672,7 @@ void MainWindow::ConnectGameList()
&MainWindow::ShowRiivolutionBootWidget);

connect(m_game_list, &GameList::OpenGeneralSettings, this, &MainWindow::ShowGeneralWindow);
connect(m_game_list, &GameList::OpenGraphicsSettings, this, &MainWindow::ShowGraphicsWindow);
}

void MainWindow::ConnectRenderWidget()
@@ -29,6 +29,7 @@
#include "Common/FileUtil.h"
#include "Common/StringUtil.h"

#include "Core/Config/GraphicsSettings.h"
#include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
@@ -351,6 +352,22 @@ bool Settings::IsKeepWindowOnTopEnabled() const
return Config::Get(Config::MAIN_KEEP_WINDOW_ON_TOP);
}

bool Settings::GetGraphicModsEnabled() const
{
return Config::Get(Config::GFX_MODS_ENABLE);
}

void Settings::SetGraphicModsEnabled(bool enabled)
{
if (GetGraphicModsEnabled() == enabled)
{
return;
}

Config::SetBaseOrCurrent(Config::GFX_MODS_ENABLE, enabled);
emit EnableGfxModsChanged(enabled);
}

int Settings::GetVolume() const
{
return Config::Get(Config::MAIN_AUDIO_VOLUME);
@@ -108,6 +108,8 @@ class Settings final : public QObject
bool GetLockCursor() const;
void SetKeepWindowOnTop(bool top);
bool IsKeepWindowOnTopEnabled() const;
bool GetGraphicModsEnabled() const;
void SetGraphicModsEnabled(bool enabled);

// Audio
int GetVolume() const;
@@ -200,6 +202,7 @@ class Settings final : public QObject
void DevicesChanged();
void SDCardInsertionChanged(bool inserted);
void USBKeyboardConnectionChanged(bool connected);
void EnableGfxModsChanged(bool enabled);

private:
Settings();
@@ -69,6 +69,7 @@ static void CreateLoadPath(std::string path)
File::SetUserPath(D_LOAD_IDX, std::move(path));
File::CreateFullPath(File::GetUserPath(D_HIRESTEXTURES_IDX));
File::CreateFullPath(File::GetUserPath(D_RIIVOLUTION_IDX));
File::CreateFullPath(File::GetUserPath(D_GRAPHICSMOD_IDX));
}

static void CreateResourcePackPath(std::string path)
@@ -188,6 +189,7 @@ void CreateDirectories()
File::CreateFullPath(File::GetUserPath(D_CACHE_IDX));
File::CreateFullPath(File::GetUserPath(D_COVERCACHE_IDX));
File::CreateFullPath(File::GetUserPath(D_CONFIG_IDX));
File::CreateFullPath(File::GetUserPath(D_CONFIG_IDX) + GRAPHICSMOD_CONFIG_DIR DIR_SEP);
File::CreateFullPath(File::GetUserPath(D_DUMPDSP_IDX));
File::CreateFullPath(File::GetUserPath(D_DUMPSSL_IDX));
File::CreateFullPath(File::GetUserPath(D_DUMPTEXTURES_IDX));
@@ -197,6 +199,7 @@ void CreateDirectories()
File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + EUR_DIR DIR_SEP);
File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + JAP_DIR DIR_SEP);
File::CreateFullPath(File::GetUserPath(D_HIRESTEXTURES_IDX));
File::CreateFullPath(File::GetUserPath(D_GRAPHICSMOD_IDX));
File::CreateFullPath(File::GetUserPath(D_MAILLOGS_IDX));
File::CreateFullPath(File::GetUserPath(D_MAPS_IDX));
File::CreateFullPath(File::GetUserPath(D_SCREENSHOTS_IDX));
@@ -39,6 +39,32 @@ add_library(videocommon
GeometryShaderGen.h
GeometryShaderManager.cpp
GeometryShaderManager.h
GraphicsModSystem/Config/GraphicsMod.cpp
GraphicsModSystem/Config/GraphicsMod.h
GraphicsModSystem/Config/GraphicsModFeature.cpp
GraphicsModSystem/Config/GraphicsModFeature.h
GraphicsModSystem/Config/GraphicsModGroup.cpp
GraphicsModSystem/Config/GraphicsModGroup.h
GraphicsModSystem/Config/GraphicsTarget.cpp
GraphicsModSystem/Config/GraphicsTarget.h
GraphicsModSystem/Config/GraphicsTargetGroup.cpp
GraphicsModSystem/Config/GraphicsTargetGroup.h
GraphicsModSystem/Constants.h
GraphicsModSystem/Runtime/Actions/MoveAction.cpp
GraphicsModSystem/Runtime/Actions/MoveAction.h
GraphicsModSystem/Runtime/Actions/PrintAction.cpp
GraphicsModSystem/Runtime/Actions/PrintAction.h
GraphicsModSystem/Runtime/Actions/ScaleAction.cpp
GraphicsModSystem/Runtime/Actions/ScaleAction.h
GraphicsModSystem/Runtime/Actions/SkipAction.cpp
GraphicsModSystem/Runtime/Actions/SkipAction.h
GraphicsModSystem/Runtime/FBInfo.cpp
GraphicsModSystem/Runtime/FBInfo.h
GraphicsModSystem/Runtime/GraphicsModAction.h
GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp
GraphicsModSystem/Runtime/GraphicsModActionFactory.h
GraphicsModSystem/Runtime/GraphicsModManager.cpp
GraphicsModSystem/Runtime/GraphicsModManager.h
HiresTextures.cpp
HiresTextures.h
HiresTextures_DDSLoader.cpp
@@ -0,0 +1,291 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"

#include <fmt/format.h>

#include "Common/CommonPaths.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"

#include "VideoCommon/GraphicsModSystem/Constants.h"

std::optional<GraphicsModConfig> GraphicsModConfig::Create(const std::string& file_path,
Source source)
{
std::string json_data;
if (!File::ReadFileToString(file_path, json_data))
{
ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod json file '{}'", file_path);
return std::nullopt;
}

picojson::value root;
const auto error = picojson::parse(root, json_data);

if (!error.empty())
{
ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod json file '{}' due to parse error: {}",
file_path, error);
return std::nullopt;
}

GraphicsModConfig result;
if (!result.DeserializeFromConfig(root))
{
return std::nullopt;
}
result.m_source = source;
if (source == Source::User)
{
const std::string base_path = File::GetUserPath(D_GRAPHICSMOD_IDX);
if (base_path.size() > file_path.size())
{
ERROR_LOG_FMT(
VIDEO,
"Failed to load graphics mod json file '{}' due to it not matching the base path: {}",
file_path, base_path);
return std::nullopt;
}
result.m_relative_path = file_path.substr(base_path.size());
}
else
{
const std::string base_path = File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR;
if (base_path.size() > file_path.size())
{
ERROR_LOG_FMT(
VIDEO,
"Failed to load graphics mod json file '{}' due to it not matching the base path: {}",
file_path, base_path);
return std::nullopt;
}
result.m_relative_path = file_path.substr(base_path.size());
}

return result;
}

std::optional<GraphicsModConfig> GraphicsModConfig::Create(const picojson::object* obj)
{
if (!obj)
return std::nullopt;

const auto source_it = obj->find("source");
if (source_it == obj->end())
{
return std::nullopt;
}
const std::string source_str = source_it->second.to_str();

const auto path_it = obj->find("path");
if (path_it == obj->end())
{
return std::nullopt;
}
const std::string relative_path = path_it->second.to_str();

if (source_str == "system")
{
return Create(fmt::format("{}{}{}", File::GetSysDirectory(), DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR,
relative_path),
Source::System);
}
else
{
return Create(File::GetUserPath(D_GRAPHICSMOD_IDX) + relative_path, Source::User);
}
}

std::string GraphicsModConfig::GetAbsolutePath() const
{
if (m_source == Source::System)
{
return WithUnifiedPathSeparators(fmt::format("{}{}{}", File::GetSysDirectory(),
DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_relative_path));
}
else
{
return WithUnifiedPathSeparators(File::GetUserPath(D_GRAPHICSMOD_IDX) + m_relative_path);
}
}

bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value)
{
const auto& meta = value.get("meta");
if (meta.is<picojson::object>())
{
const auto& title = meta.get("title");
if (title.is<std::string>())
{
m_title = title.to_str();
}

const auto& author = meta.get("author");
if (author.is<std::string>())
{
m_author = author.to_str();
}

const auto& description = meta.get("description");
if (description.is<std::string>())
{
m_description = description.to_str();
}
}

const auto& groups = value.get("groups");
if (groups.is<picojson::array>())
{
for (const auto& group_val : groups.get<picojson::array>())
{
if (!group_val.is<picojson::object>())
{
ERROR_LOG_FMT(
VIDEO, "Failed to load mod configuration file, specified group is not a json object");
return false;
}
GraphicsTargetGroupConfig group;
if (!group.DeserializeFromConfig(group_val.get<picojson::object>()))
{
return false;
}

m_groups.push_back(group);
}
}

const auto& features = value.get("features");
if (features.is<picojson::array>())
{
for (const auto& feature_val : features.get<picojson::array>())
{
if (!feature_val.is<picojson::object>())
{
ERROR_LOG_FMT(
VIDEO, "Failed to load mod configuration file, specified feature is not a json object");
return false;
}
GraphicsModFeatureConfig feature;
if (!feature.DeserializeFromConfig(feature_val.get<picojson::object>()))
{
return false;
}

m_features.push_back(feature);
}
}

return true;
}

void GraphicsModConfig::SerializeToProfile(picojson::object* obj) const
{
if (!obj)
return;

auto& json_obj = *obj;
switch (m_source)
{
case Source::User:
{
json_obj["source"] = picojson::value{"user"};
}
break;
case Source::System:
{
json_obj["source"] = picojson::value{"system"};
}
break;
};

json_obj["path"] = picojson::value{m_relative_path};

picojson::array serialized_groups;
for (const auto& group : m_groups)
{
picojson::object serialized_group;
group.SerializeToProfile(&serialized_group);
serialized_groups.push_back(picojson::value{serialized_group});
}
json_obj["groups"] = picojson::value{serialized_groups};

picojson::array serialized_features;
for (const auto& feature : m_features)
{
picojson::object serialized_feature;
feature.SerializeToProfile(&serialized_feature);
serialized_features.push_back(picojson::value{serialized_feature});
}
json_obj["features"] = picojson::value{serialized_features};

json_obj["enabled"] = picojson::value{m_enabled};

json_obj["weight"] = picojson::value{static_cast<double>(m_weight)};
}

void GraphicsModConfig::DeserializeFromProfile(const picojson::object& obj)
{
if (const auto it = obj.find("groups"); it != obj.end())
{
if (it->second.is<picojson::array>())
{
auto serialized_groups = it->second.get<picojson::array>();
if (serialized_groups.size() != m_groups.size())
return;

for (std::size_t i = 0; i < serialized_groups.size(); i++)
{
const auto& serialized_group_val = serialized_groups[i];
if (serialized_group_val.is<picojson::object>())
{
const auto& serialized_group = serialized_group_val.get<picojson::object>();
m_groups[i].DeserializeFromProfile(serialized_group);
}
}
}
}

if (const auto it = obj.find("features"); it != obj.end())
{
if (it->second.is<picojson::array>())
{
auto serialized_features = it->second.get<picojson::array>();
if (serialized_features.size() != m_features.size())
return;

for (std::size_t i = 0; i < serialized_features.size(); i++)
{
const auto& serialized_feature_val = serialized_features[i];
if (serialized_feature_val.is<picojson::object>())
{
const auto& serialized_feature = serialized_feature_val.get<picojson::object>();
m_features[i].DeserializeFromProfile(serialized_feature);
}
}
}
}

if (const auto it = obj.find("enabled"); it != obj.end())
{
if (it->second.is<bool>())
{
m_enabled = it->second.get<bool>();
}
}

if (const auto it = obj.find("weight"); it != obj.end())
{
if (it->second.is<double>())
{
m_weight = static_cast<u16>(it->second.get<double>());
}
}
}

bool GraphicsModConfig::operator<(const GraphicsModConfig& other) const
{
return m_weight < other.m_weight;
}
@@ -0,0 +1,45 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <optional>
#include <string>
#include <vector>

#include <picojson.h>

#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h"
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h"

struct GraphicsModConfig
{
std::string m_title;
std::string m_author;
std::string m_description;
bool m_enabled = false;
u16 m_weight = 0;
std::string m_relative_path;

enum class Source
{
User,
System
};
Source m_source = Source::User;

std::vector<GraphicsTargetGroupConfig> m_groups;
std::vector<GraphicsModFeatureConfig> m_features;

static std::optional<GraphicsModConfig> Create(const std::string& file, Source source);
static std::optional<GraphicsModConfig> Create(const picojson::object* obj);

std::string GetAbsolutePath() const;

bool DeserializeFromConfig(const picojson::value& value);

void SerializeToProfile(picojson::object* value) const;
void DeserializeFromProfile(const picojson::object& value);

bool operator<(const GraphicsModConfig& other) const;
};
@@ -0,0 +1,48 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h"

#include "Common/Logging/Log.h"

bool GraphicsModFeatureConfig::DeserializeFromConfig(const picojson::object& obj)
{
if (auto group_iter = obj.find("group"); group_iter != obj.end())
{
if (!group_iter->second.is<std::string>())
{
ERROR_LOG_FMT(
VIDEO,
"Failed to load mod configuration file, specified feature's group is not a string");
return false;
}
m_group = group_iter->second.get<std::string>();
}

if (auto action_iter = obj.find("action"); action_iter != obj.end())
{
if (!action_iter->second.is<std::string>())
{
ERROR_LOG_FMT(
VIDEO,
"Failed to load mod configuration file, specified feature's action is not a string");
return false;
}
m_action = action_iter->second.get<std::string>();
}

if (auto action_data_iter = obj.find("action_data"); action_data_iter != obj.end())
{
m_action_data = action_data_iter->second;
}

return true;
}

void GraphicsModFeatureConfig::SerializeToProfile(picojson::object*) const
{
}

void GraphicsModFeatureConfig::DeserializeFromProfile(const picojson::object&)
{
}
@@ -0,0 +1,20 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <string>

#include <picojson.h>

struct GraphicsModFeatureConfig
{
std::string m_group;
std::string m_action;
picojson::value m_action_data;

bool DeserializeFromConfig(const picojson::object& value);

void SerializeToProfile(picojson::object* value) const;
void DeserializeFromProfile(const picojson::object& value);
};
@@ -0,0 +1,191 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"

#include <map>
#include <sstream>
#include <string>

#include "Common/CommonPaths.h"
#include "Common/FileSearch.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"

#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
#include "VideoCommon/GraphicsModSystem/Constants.h"
#include "VideoCommon/HiresTextures.h"

GraphicsModGroupConfig::GraphicsModGroupConfig(const std::string& game_id) : m_game_id(game_id)
{
}

GraphicsModGroupConfig::~GraphicsModGroupConfig() = default;

GraphicsModGroupConfig::GraphicsModGroupConfig(const GraphicsModGroupConfig&) = default;

GraphicsModGroupConfig::GraphicsModGroupConfig(GraphicsModGroupConfig&&) = default;

GraphicsModGroupConfig& GraphicsModGroupConfig::operator=(const GraphicsModGroupConfig&) = default;

GraphicsModGroupConfig& GraphicsModGroupConfig::operator=(GraphicsModGroupConfig&&) = default;

void GraphicsModGroupConfig::Load()
{
const std::string file_path = GetPath();

std::set<std::string> known_paths;
if (File::Exists(file_path))
{
std::string json_data;
if (!File::ReadFileToString(file_path, json_data))
{
ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod group json file '{}'", file_path);
return;
}

picojson::value root;
const auto error = picojson::parse(root, json_data);

if (!error.empty())
{
ERROR_LOG_FMT(VIDEO,
"Failed to load graphics mod group json file '{}' due to parse error: {}",
file_path, error);
return;
}
if (!root.is<picojson::object>())
{
ERROR_LOG_FMT(
VIDEO,
"Failed to load graphics mod group json file '{}' due to root not being an object!",
file_path);
return;
}

const auto& mods = root.get("mods");
if (mods.is<picojson::array>())
{
for (const auto& mod_json : mods.get<picojson::array>())
{
if (mod_json.is<picojson::object>())
{
const auto& mod_json_obj = mod_json.get<picojson::object>();
auto graphics_mod = GraphicsModConfig::Create(&mod_json_obj);
if (!graphics_mod)
{
continue;
}
graphics_mod->DeserializeFromProfile(mod_json_obj);

auto mod_full_path = graphics_mod->GetAbsolutePath();
known_paths.insert(std::move(mod_full_path));
m_graphics_mods.push_back(*graphics_mod);
}
}
}
}

const auto try_add_mod = [&known_paths, this](const std::string& dir,
GraphicsModConfig::Source source) {
auto file = dir + DIR_SEP + "metadata.json";
UnifyPathSeparators(file);
if (known_paths.find(file) != known_paths.end())
{
return;
}
const auto mod = GraphicsModConfig::Create(file, source);
if (mod)
{
m_graphics_mods.push_back(*mod);
}
};

const std::set<std::string> graphics_mod_user_directories =
GetTextureDirectoriesWithGameId(File::GetUserPath(D_GRAPHICSMOD_IDX), m_game_id);

for (const auto& graphics_mod_directory : graphics_mod_user_directories)
{
try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::User);
}

const std::set<std::string> graphics_mod_system_directories = GetTextureDirectoriesWithGameId(
File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_game_id);

for (const auto& graphics_mod_directory : graphics_mod_system_directories)
{
try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::System);
}

std::sort(m_graphics_mods.begin(), m_graphics_mods.end());
for (auto& mod : m_graphics_mods)
{
m_path_to_graphics_mod[mod.GetAbsolutePath()] = &mod;
}

m_change_count++;
}

void GraphicsModGroupConfig::Save() const
{
const std::string file_path = GetPath();
std::ofstream json_stream;
File::OpenFStream(json_stream, file_path, std::ios_base::out);
if (!json_stream.is_open())
{
ERROR_LOG_FMT(VIDEO, "Failed to open graphics mod group json file '{}' for writing", file_path);
return;
}

picojson::object serialized_root;
picojson::array serialized_mods;
for (const auto& mod : m_graphics_mods)
{
picojson::object serialized_mod;
mod.SerializeToProfile(&serialized_mod);
serialized_mods.push_back(picojson::value{serialized_mod});
}
serialized_root["mods"] = picojson::value{serialized_mods};

const auto output = picojson::value{serialized_root}.serialize(true);
json_stream << output;
}

void GraphicsModGroupConfig::SetChangeCount(u32 change_count)
{
m_change_count = change_count;
}

u32 GraphicsModGroupConfig::GetChangeCount() const
{
return m_change_count;
}

const std::vector<GraphicsModConfig>& GraphicsModGroupConfig::GetMods() const
{
return m_graphics_mods;
}

GraphicsModConfig* GraphicsModGroupConfig::GetMod(const std::string& absolute_path) const
{
if (const auto iter = m_path_to_graphics_mod.find(absolute_path);
iter != m_path_to_graphics_mod.end())
{
return iter->second;
}

return nullptr;
}

const std::string& GraphicsModGroupConfig::GetGameID() const
{
return m_game_id;
}

std::string GraphicsModGroupConfig::GetPath() const
{
const std::string game_mod_root = File::GetUserPath(D_CONFIG_IDX) + GRAPHICSMOD_CONFIG_DIR;
return fmt::format("{}/{}.json", game_mod_root, m_game_id);
}
@@ -0,0 +1,46 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <map>
#include <string>
#include <vector>

#include <picojson.h>

#include "Common/CommonTypes.h"

struct GraphicsModConfig;

class GraphicsModGroupConfig
{
public:
explicit GraphicsModGroupConfig(const std::string& game_id);
~GraphicsModGroupConfig();

GraphicsModGroupConfig(const GraphicsModGroupConfig&);
GraphicsModGroupConfig(GraphicsModGroupConfig&&);

GraphicsModGroupConfig& operator=(const GraphicsModGroupConfig&);
GraphicsModGroupConfig& operator=(GraphicsModGroupConfig&&);

void Load();
void Save() const;

void SetChangeCount(u32 change_count);
u32 GetChangeCount() const;

const std::vector<GraphicsModConfig>& GetMods() const;

GraphicsModConfig* GetMod(const std::string& absolute_path) const;

const std::string& GetGameID() const;

private:
std::string GetPath() const;
std::string m_game_id;
std::vector<GraphicsModConfig> m_graphics_mods;
std::map<std::string, GraphicsModConfig*> m_path_to_graphics_mod;
u32 m_change_count = 0;
};
@@ -0,0 +1,254 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h"

#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "VideoCommon/TextureCacheBase.h"

namespace
{
template <typename T, std::enable_if_t<std::is_base_of_v<FBTarget, T>, int> = 0>
std::optional<T> DeserializeFBTargetFromConfig(const picojson::object& obj, std::string_view prefix)
{
T fb;
const auto texture_filename_iter = obj.find("texture_filename");
if (texture_filename_iter == obj.end())
{
ERROR_LOG_FMT(VIDEO,
"Failed to load mod configuration file, option 'texture_filename' not found");
return std::nullopt;
}
if (!texture_filename_iter->second.is<std::string>())
{
ERROR_LOG_FMT(
VIDEO,
"Failed to load mod configuration file, option 'texture_filename' is not a string type");
return std::nullopt;
}
const auto texture_filename = texture_filename_iter->second.get<std::string>();
const auto texture_filename_without_prefix = texture_filename.substr(prefix.size() + 1);
const auto split_str_values = SplitString(texture_filename_without_prefix, '_');
if (split_str_values.size() == 1)
{
ERROR_LOG_FMT(
VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is not valid");
return std::nullopt;
}
const auto split_width_height_values = SplitString(texture_filename_without_prefix, 'x');
if (split_width_height_values.size() != 2)
{
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
"not valid, width and height separator found more matches than expected");
return std::nullopt;
}

const std::size_t width_underscore_pos = split_width_height_values[0].find_last_of('_');
std::string width_str;
if (width_underscore_pos == std::string::npos)
{
width_str = split_width_height_values[0];
}
else
{
width_str = split_width_height_values[0].substr(width_underscore_pos + 1);
}
if (!TryParse(width_str, &fb.m_width))
{
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
"not valid, width not a number");
return std::nullopt;
}

const std::size_t height_underscore_pos = split_width_height_values[1].find_first_of('_');
if (height_underscore_pos == std::string::npos ||
height_underscore_pos == split_width_height_values[1].size() - 1)
{
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
"not valid, underscore after height is missing or incomplete");
return std::nullopt;
}
const std::string height_str = split_width_height_values[1].substr(0, height_underscore_pos);
if (!TryParse(height_str, &fb.m_height))
{
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
"not valid, height not a number");
return std::nullopt;
}

const std::size_t format_underscore_pos =
split_width_height_values[1].find_first_of('_', height_underscore_pos + 1);

std::string format_str;
if (format_underscore_pos == std::string::npos)
{
format_str = split_width_height_values[1].substr(height_underscore_pos + 1);
}
else
{
format_str = split_width_height_values[1].substr(
height_underscore_pos + 1, (format_underscore_pos - height_underscore_pos) - 1);
}
u32 format;
if (!TryParse(format_str, &format))
{
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
"not valid, texture format is not a number");
return std::nullopt;
}
if (!IsValidTextureFormat(static_cast<TextureFormat>(format)))
{
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
"not valid, texture format is not valid");
return std::nullopt;
}
fb.m_texture_format = static_cast<TextureFormat>(format);

return fb;
}
std::optional<std::string> ExtractTextureFilenameForConfig(const picojson::object& obj)
{
const auto texture_filename_iter = obj.find("texture_filename");
if (texture_filename_iter == obj.end())
{
ERROR_LOG_FMT(VIDEO,
"Failed to load mod configuration file, option 'texture_filename' not found");
return std::nullopt;
}
if (!texture_filename_iter->second.is<std::string>())
{
ERROR_LOG_FMT(
VIDEO,
"Failed to load mod configuration file, option 'texture_filename' is not a string type");
return std::nullopt;
}
std::string texture_info = texture_filename_iter->second.get<std::string>();
if (texture_info.find(EFB_DUMP_PREFIX) != std::string::npos)
{
const auto letter_c_pos = texture_info.find_first_of('n');
if (letter_c_pos == std::string::npos)
{
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' "
"is an efb without a count");
return std::nullopt;
}
texture_info =
texture_info.substr(letter_c_pos - 1, texture_info.find_first_of("_", letter_c_pos));
}
else if (texture_info.find(XFB_DUMP_PREFIX) != std::string::npos)
{
const auto letter_c_pos = texture_info.find_first_of('n');
if (letter_c_pos == std::string::npos)
{
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' "
"is an xfb without a count");
return std::nullopt;
}
texture_info =
texture_info.substr(letter_c_pos - 1, texture_info.find_first_of("_", letter_c_pos));
}
return texture_info;
}
} // namespace

std::optional<GraphicsTargetConfig> DeserializeTargetFromConfig(const picojson::object& obj)
{
const auto type_iter = obj.find("type");
if (type_iter == obj.end())
{
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'type' not found");
return std::nullopt;
}
if (!type_iter->second.is<std::string>())
{
ERROR_LOG_FMT(VIDEO,
"Failed to load mod configuration file, option 'type' is not a string type");
return std::nullopt;
}
const std::string& type = type_iter->second.get<std::string>();
if (type == "draw_started")
{
std::optional<std::string> texture_info = ExtractTextureFilenameForConfig(obj);
if (!texture_info.has_value())
return std::nullopt;

DrawStartedTextureTarget target;
target.m_texture_info_string = texture_info.value();
return target;
}
else if (type == "load_texture")
{
std::optional<std::string> texture_info = ExtractTextureFilenameForConfig(obj);
if (!texture_info.has_value())
return std::nullopt;

LoadTextureTarget target;
target.m_texture_info_string = texture_info.value();
return target;
}
else if (type == "efb")
{
return DeserializeFBTargetFromConfig<EFBTarget>(obj, EFB_DUMP_PREFIX);
}
else if (type == "xfb")
{
return DeserializeFBTargetFromConfig<XFBTarget>(obj, EFB_DUMP_PREFIX);
}
else if (type == "projection")
{
ProjectionTarget target;
const auto texture_iter = obj.find("texture_filename");
if (texture_iter != obj.end())
{
std::optional<std::string> texture_info = ExtractTextureFilenameForConfig(obj);
if (!texture_info.has_value())
return std::nullopt;
target.m_texture_info_string = texture_info;
}
const auto value_iter = obj.find("value");
if (value_iter == obj.end())
{
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'value' not found");
return std::nullopt;
}
if (!value_iter->second.is<std::string>())
{
ERROR_LOG_FMT(VIDEO,
"Failed to load mod configuration file, option 'value' is not a string type");
return std::nullopt;
}
const auto& value_str = value_iter->second.get<std::string>();
if (value_str == "2d")
{
target.m_projection_type = ProjectionType::Orthographic;
}
else if (value_str == "3d")
{
target.m_projection_type = ProjectionType::Perspective;
}
else
{
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'value' is not a valid "
"value, valid values are: 2d, 3d");
return std::nullopt;
}
return target;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Failed to load mod configuration file, option 'type' is not a valid value");
}
return std::nullopt;
}

void SerializeTargetToProfile(picojson::object*, const GraphicsTargetConfig&)
{
// Added for consistency, no functionality as of now
}

void DeserializeTargetFromProfile(const picojson::object&, GraphicsTargetConfig*)
{
// Added for consistency, no functionality as of now
}
@@ -0,0 +1,56 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <optional>
#include <string>
#include <variant>

#include <picojson.h>

#include "Common/CommonTypes.h"
#include "VideoCommon/TextureDecoder.h"
#include "VideoCommon/XFMemory.h"

struct TextureTarget
{
std::string m_texture_info_string;
};

struct DrawStartedTextureTarget final : public TextureTarget
{
};

struct LoadTextureTarget final : public TextureTarget
{
};

struct FBTarget
{
u32 m_height = 0;
u32 m_width = 0;
TextureFormat m_texture_format = TextureFormat::I4;
};

struct EFBTarget final : public FBTarget
{
};

struct XFBTarget final : public FBTarget
{
};

struct ProjectionTarget
{
std::optional<std::string> m_texture_info_string;
ProjectionType m_projection_type = ProjectionType::Perspective;
};

using GraphicsTargetConfig = std::variant<DrawStartedTextureTarget, LoadTextureTarget, EFBTarget,
XFBTarget, ProjectionTarget>;

std::optional<GraphicsTargetConfig> DeserializeTargetFromConfig(const picojson::object& obj);

void SerializeTargetToProfile(picojson::object* obj, const GraphicsTargetConfig& target);
void DeserializeTargetFromProfile(const picojson::object& obj, GraphicsTargetConfig* target);
@@ -0,0 +1,88 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h"

#include "Common/Logging/Log.h"

bool GraphicsTargetGroupConfig::DeserializeFromConfig(const picojson::object& obj)
{
if (auto name_iter = obj.find("name"); name_iter != obj.end())
{
if (!name_iter->second.is<std::string>())
{
ERROR_LOG_FMT(
VIDEO, "Failed to load mod configuration file, specified group's name is not a string");
return false;
}
m_name = name_iter->second.get<std::string>();
}

if (auto targets_iter = obj.find("targets"); targets_iter != obj.end())
{
if (!targets_iter->second.is<picojson::array>())
{
ERROR_LOG_FMT(
VIDEO,
"Failed to load mod configuration file, specified group's targets is not an array");
return false;
}
for (const auto& target_val : targets_iter->second.get<picojson::array>())
{
if (!target_val.is<picojson::object>())
{
ERROR_LOG_FMT(
VIDEO,
"Failed to load shader configuration file, specified target is not a json object");
return false;
}
const auto target = DeserializeTargetFromConfig(target_val.get<picojson::object>());
if (!target)
{
return false;
}

m_targets.push_back(*target);
}
}

return true;
}

void GraphicsTargetGroupConfig::SerializeToProfile(picojson::object* obj) const
{
if (!obj)
return;
auto& json_obj = *obj;
picojson::array serialized_targets;
for (const auto& target : m_targets)
{
picojson::object serialized_target;
SerializeTargetToProfile(&serialized_target, target);
serialized_targets.push_back(picojson::value{serialized_target});
}
json_obj["targets"] = picojson::value{serialized_targets};
}

void GraphicsTargetGroupConfig::DeserializeFromProfile(const picojson::object& obj)
{
if (const auto it = obj.find("targets"); it != obj.end())
{
if (it->second.is<picojson::array>())
{
auto serialized_targets = it->second.get<picojson::array>();
if (serialized_targets.size() != m_targets.size())
return;

for (std::size_t i = 0; i < serialized_targets.size(); i++)
{
const auto& serialized_target_val = serialized_targets[i];
if (serialized_target_val.is<picojson::object>())
{
const auto& serialized_target = serialized_target_val.get<picojson::object>();
DeserializeTargetFromProfile(serialized_target, &m_targets[i]);
}
}
}
}
}
@@ -0,0 +1,22 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <string>
#include <vector>

#include <picojson.h>

#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h"

struct GraphicsTargetGroupConfig
{
std::string m_name;
std::vector<GraphicsTargetConfig> m_targets;

bool DeserializeFromConfig(const picojson::object& obj);

void SerializeToProfile(picojson::object* obj) const;
void DeserializeFromProfile(const picojson::object& obj);
};
@@ -0,0 +1,11 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <string>

#include "Common/CommonPaths.h"

static const inline std::string DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR =
LOAD_DIR DIR_SEP GRAPHICSMOD_DIR DIR_SEP;
@@ -0,0 +1,47 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h"

std::unique_ptr<MoveAction> MoveAction::Create(const picojson::value& json_data)
{
Common::Vec3 position_offset;
const auto& x = json_data.get("X");
if (x.is<double>())
{
position_offset.x = static_cast<float>(x.get<double>());
}

const auto& y = json_data.get("Y");
if (y.is<double>())
{
position_offset.y = static_cast<float>(y.get<double>());
}

const auto& z = json_data.get("Z");
if (z.is<double>())
{
position_offset.z = static_cast<float>(z.get<double>());
}
return std::make_unique<MoveAction>(position_offset);
}

MoveAction::MoveAction(Common::Vec3 position_offset) : m_position_offset(position_offset)
{
}

void MoveAction::OnProjection(Common::Matrix44* matrix)
{
if (!matrix)
return;

*matrix *= Common::Matrix44::Translate(m_position_offset);
}

void MoveAction::OnProjectionAndTexture(Common::Matrix44* matrix)
{
if (!matrix)
return;

*matrix *= Common::Matrix44::Translate(m_position_offset);
}
@@ -0,0 +1,22 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>

#include <picojson.h>

#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"

class MoveAction final : public GraphicsModAction
{
public:
static std::unique_ptr<MoveAction> Create(const picojson::value& json_data);
explicit MoveAction(Common::Vec3 position_offset);
void OnProjection(Common::Matrix44* matrix) override;
void OnProjectionAndTexture(Common::Matrix44* matrix) override;

private:
Common::Vec3 m_position_offset;
};
@@ -0,0 +1,36 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h"

#include "Common/Logging/Log.h"

void PrintAction::OnDrawStarted(bool*)
{
INFO_LOG_FMT(VIDEO, "OnDrawStarted Called");
}

void PrintAction::OnEFB(bool*, u32 texture_width, u32 texture_height, u32* scaled_width,
u32* scaled_height)
{
if (!scaled_width || !scaled_height)
return;

INFO_LOG_FMT(VIDEO, "OnEFB Called. Original [{}, {}], Scaled [{}, {}]", texture_width,
texture_height, *scaled_width, *scaled_height);
}

void PrintAction::OnProjection(Common::Matrix44*)
{
INFO_LOG_FMT(VIDEO, "OnProjection Called");
}

void PrintAction::OnProjectionAndTexture(Common::Matrix44*)
{
INFO_LOG_FMT(VIDEO, "OnProjectionAndTexture Called");
}

void PrintAction::OnTextureLoad()
{
INFO_LOG_FMT(VIDEO, "OnTextureLoad Called");
}
@@ -0,0 +1,17 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"

class PrintAction final : public GraphicsModAction
{
public:
void OnDrawStarted(bool* skip) override;
void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width,
u32* scaled_height) override;
void OnProjection(Common::Matrix44* matrix) override;
void OnProjectionAndTexture(Common::Matrix44* matrix) override;
void OnTextureLoad() override;
};
@@ -0,0 +1,59 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h"

std::unique_ptr<ScaleAction> ScaleAction::Create(const picojson::value& json_data)
{
Common::Vec3 scale;
const auto& x = json_data.get("X");
if (x.is<double>())
{
scale.x = static_cast<float>(x.get<double>());
}

const auto& y = json_data.get("Y");
if (y.is<double>())
{
scale.y = static_cast<float>(y.get<double>());
}

const auto& z = json_data.get("Z");
if (z.is<double>())
{
scale.z = static_cast<float>(z.get<double>());
}
return std::make_unique<ScaleAction>(scale);
}

ScaleAction::ScaleAction(Common::Vec3 scale) : m_scale(scale)
{
}

void ScaleAction::OnEFB(bool*, u32 texture_width, u32 texture_height, u32* scaled_width,
u32* scaled_height)
{
if (scaled_width && m_scale.x > 0)
*scaled_width = texture_width * m_scale.x;

if (scaled_height && m_scale.y > 0)
*scaled_height = texture_height * m_scale.y;
}

void ScaleAction::OnProjection(Common::Matrix44* matrix)
{
if (!matrix)
return;
auto& the_matrix = *matrix;
the_matrix.data[0] = the_matrix.data[0] * m_scale.x;
the_matrix.data[5] = the_matrix.data[5] * m_scale.y;
}

void ScaleAction::OnProjectionAndTexture(Common::Matrix44* matrix)
{
if (!matrix)
return;
auto& the_matrix = *matrix;
the_matrix.data[0] = the_matrix.data[0] * m_scale.x;
the_matrix.data[5] = the_matrix.data[5] * m_scale.y;
}
@@ -0,0 +1,24 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>

#include <picojson.h>

#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"

class ScaleAction final : public GraphicsModAction
{
public:
static std::unique_ptr<ScaleAction> Create(const picojson::value& json_data);
explicit ScaleAction(Common::Vec3 scale);
void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width,
u32* scaled_height) override;
void OnProjection(Common::Matrix44* matrix) override;
void OnProjectionAndTexture(Common::Matrix44* matrix) override;

private:
Common::Vec3 m_scale;
};
@@ -0,0 +1,20 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h"

void SkipAction::OnDrawStarted(bool* skip)
{
if (!skip)
return;

*skip = true;
}

void SkipAction::OnEFB(bool* skip, u32, u32, u32*, u32*)
{
if (!skip)
return;

*skip = true;
}
@@ -0,0 +1,14 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"

class SkipAction final : public GraphicsModAction
{
public:
void OnDrawStarted(bool* skip) override;
void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width,
u32* scaled_height) override;
};
@@ -0,0 +1,22 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h"

#include "Common/Hash.h"

u32 FBInfo::CalculateHash() const
{
return Common::HashAdler32(reinterpret_cast<const u8*>(this), sizeof(FBInfo));
}

bool FBInfo::operator==(const FBInfo& other) const
{
return m_height == other.m_height && m_width == other.m_width &&
m_texture_format == other.m_texture_format;
}

bool FBInfo::operator!=(const FBInfo& other) const
{
return !(*this == other);
}
@@ -0,0 +1,25 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "Common/CommonTypes.h"
#include "VideoCommon/TextureDecoder.h"

struct FBInfo
{
u32 m_height = 0;
u32 m_width = 0;
TextureFormat m_texture_format = TextureFormat::I4;
u32 CalculateHash() const;
bool operator==(const FBInfo& other) const;
bool operator!=(const FBInfo& other) const;
};

struct FBInfoHasher
{
std::size_t operator()(const FBInfo& fb_info) const noexcept
{
return static_cast<std::size_t>(fb_info.CalculateHash());
}
};
@@ -0,0 +1,29 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "Common/CommonTypes.h"
#include "Common/Matrix.h"

class GraphicsModAction
{
public:
GraphicsModAction() = default;
virtual ~GraphicsModAction() = default;
GraphicsModAction(const GraphicsModAction&) = default;
GraphicsModAction(GraphicsModAction&&) = default;
GraphicsModAction& operator=(const GraphicsModAction&) = default;
GraphicsModAction& operator=(GraphicsModAction&&) = default;

virtual void OnDrawStarted(bool* skip) {}
virtual void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width,
u32* scaled_height)
{
}
virtual void OnXFB() {}
virtual void OnProjection(Common::Matrix44* matrix) {}
virtual void OnProjectionAndTexture(Common::Matrix44* matrix) {}
virtual void OnTextureLoad() {}
virtual void OnFrameEnd() {}
};
@@ -0,0 +1,34 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"

#include "VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h"
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h"
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h"
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h"

namespace GraphicsModActionFactory
{
std::unique_ptr<GraphicsModAction> Create(std::string_view name, const picojson::value& json_data)
{
if (name == "print")
{
return std::make_unique<PrintAction>();
}
else if (name == "skip")
{
return std::make_unique<SkipAction>();
}
else if (name == "move")
{
return MoveAction::Create(json_data);
}
else if (name == "scale")
{
return ScaleAction::Create(json_data);
}

return nullptr;
}
} // namespace GraphicsModActionFactory
@@ -0,0 +1,16 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>
#include <string_view>

#include <picojson.h>

#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"

namespace GraphicsModActionFactory
{
std::unique_ptr<GraphicsModAction> Create(std::string_view name, const picojson::value& json_data);
}