This commit is contained in:
crediar 2025-08-02 11:33:18 +02:00
commit fdf5df80f8
83 changed files with 960 additions and 443 deletions

File diff suppressed because one or more lines are too long

View file

@ -59,9 +59,12 @@ C202F310 00000003
$16:9 Aspect Ratio Fix - Centered HUD with letterboxing
04199598 4E800020
0441F6D8 3FE38E39
$No Letterbox [Ralf]
0400E3D4 48000070
[Gecko_RetroAchievements_Verified]
$16:9 Aspect Ratio Fix - Normal HUD
$16:9 Aspect Ratio Fix - Centered HUD
$16:9 Aspect Ratio Fix - Stretched HUD
$16:9 Aspect Ratio Fix - Centered HUD with letterboxing
$No Letterbox

View file

@ -32,9 +32,12 @@ C202F3F8 00000003
$16:9 Aspect Ratio Fix - Centered HUD with letterboxing
0419B274 4E800020
0442C158 3FE38E39
$No Letterbox [Ralf]
0400E598 48000070
[Gecko_RetroAchievements_Verified]
$16:9 Aspect Ratio Fix - Normal HUD
$16:9 Aspect Ratio Fix - Centered HUD
$16:9 Aspect Ratio Fix - Stretched HUD
$16:9 Aspect Ratio Fix - Centered HUD with letterboxing
$No Letterbox

View file

@ -1,4 +1,4 @@
# GC6E01, GC6P01 - Pokemon Colosseum
# GC6E01, GC6P01, GC6J01 - Pokemon Colosseum
[Core]
# Values set here will override the main Dolphin settings.
@ -13,5 +13,5 @@
SafeTextureCacheColorSamples = 0
# Many areas of the game have unused vertexes, especially with cutscenes
# involving Shadow Pokémon, such as the purification cutscene.
# CPU Cull ends up greatly boosting performance for these cases.
# CPU Cull ends up greatly boosting performance for these cases.
CPUCull = True

View file

@ -36,9 +36,12 @@ $16:9 Widescreen
04005308 4809E62C
040A3930 4BF619D0
0447E724 3FAAAAAB
$60 FPS [Nerdzilla]
04005D98 38600000
[Patches_RetroAchievements_Verified]
$Allow Memory Card saving with Savestates
[AR_RetroAchievements_Verified]
$16:9 Widescreen
$60 FPS [Nerdzilla]

View file

@ -30,27 +30,11 @@ $Allow Memory Card saving with Savestates
$Allow Memory Card saving with Savestates
[ActionReplay]
$16:9 Widescreen
04261AC0 000034E0
04261AC4 000034E4
F6000001 80008180
FFA01090 93E10024
D2000000 00000003
3DC03FAA 61CEAAAB
91C20000 C2620000
EFB300B2 00000000
E0000000 80008000
F6000001 80008180
FF601090 7C7F1B78
D2000000 00000004
3DC03FAA 61CEAAAB
91C20004 C2220004
EF7100B2 39C00000
60000000 00000000
E0000000 80008000
$60 FPS [Nerdzilla]
04005CFC 38600000
[Patches_RetroAchievements_Verified]
$Allow Memory Card saving with Savestates
[AR_RetroAchievements_Verified]
$16:9 Widescreen
$60 FPS [Nerdzilla]

View file

@ -36,9 +36,12 @@ $16:9 Widescreen [Ralf]
04005308 480A1C68
040A6F6C 4BF5E394
044CBCA4 3FAAAAAB
$60 FPS [Nerdzilla]
04005E90 38600000
[Patches_RetroAchievements_Verified]
$Allow Memory Card saving with Savestates
[AR_RetroAchievements_Verified]
$16:9 Widescreen [Ralf]
$60 FPS [Nerdzilla]

View file

@ -0,0 +1,17 @@
# GD7PB2 - Dragon Ball Z: Budokai
[Core]
# Values set here will override the main Dolphin settings.
[OnFrame]
# Add memory patches to be applied every frame here.
[ActionReplay]
# Add action replay cheats here.
[Gecko]
$Deinterlacing Fix
0044E9A8 00000000
[Gecko_RetroAchievements_Verified]
$Deinterlacing Fix

View file

@ -101,23 +101,28 @@ C2434EBC 00000002
3DC03F36 91DC00CC
60000000 00000000
$60 FPS
04423540 3BC00001 # `setFramerate`: force 60 FPS
0451D804 40100000 # Map translation speed 1
C2312B04 00000002 # Map translation speed 2
3C003F80 9001FFFC
C121FFFC 00000000
C242DF28 00000004 # Set to 30 FPS when cutscene starts
3C608042 6063352C
7C6903A6 806D9AEC
38800002 4E800421
801F01F0 00000000
C242E040 00000004 # Set to 60 FPS when cutscene ends
3C608042 6063352C
7C6903A6 806D9AEC
38800001 4E800421
801F01F0 00000000
0414B778 38800001 # `BaseGameSection::init`, force `setFramerate` argument to 60 FPS (2P mode doesn't start with a cutscene)
04513C5C 40100000 # Map translation speed 1
04513C60 3F800000 # Map translation speed 2
0451D7B0 3C75C28F # Map scale speed
04520628 3F911111 # Cutscene delta time
0452062C 20000000
C2010654 00000002 # Double cutscene sections' "frame duration"
5484083C 909F002C
60000000 00000000
C242ED28 00000002 # Cutscene: objects spawn/despawn timing
3C003F00 9001FFFC
C001FFFC 00000000
C24374DC 00000002 # Cutscene: trigger `startFadeblack` a frame earlier (fixes black screen in Day 1 cutscene)
806D9AEC C0230054
FC020840 00000000
04514128 3ECCCCCD # Final floor animation
04516140 3F000000 # Pay dept animation
04520078 3EB33333 # Ready-go animation
04410FA4 388000C8 # Win/Lose reason duration 1
04410FAC 386000FA # Win/Lose reason duration 2
04520224 3F000000 # Win/Lose animation
044106DC 38800078 # Win/Lose duration 1
044106E4 38600168 # Win/Lose duration 2
[Gecko_RetroAchievements_Verified]
$16:9 Widescreen

View file

@ -8,7 +8,7 @@
$16:9 Widescreen
043438A8 3FAF0000
043486A8 406CB800
$60Hz
$60 FPS
040B66F8 60000000
0402F0E4 38600002
0402F1CC 60000000
@ -16,4 +16,4 @@ $60Hz
[AR_RetroAchievements_Verified]
$16:9 Widescreen
$60Hz
$60 FPS

View file

@ -8,7 +8,7 @@
$16:9 Widescreen
04343EC8 3FAF0000
04348D30 406CB800
$60Hz
$60 FPS
040B6F08 60000000
0402F130 38600002
0402F218 60000000
@ -16,4 +16,4 @@ $60Hz
[AR_RetroAchievements_Verified]
$16:9 Widescreen
$60Hz
$60 FPS

View file

@ -8,7 +8,7 @@
$16:9 Widescreen
04336CE8 3FABA000
0433B920 406C4F00
$50Hz
$50 FPS
040B6408 60000000
0402F214 38600001
0402F300 60000000
@ -16,4 +16,4 @@ $50Hz
[AR_RetroAchievements_Verified]
$16:9 Widescreen
$50Hz
$50 FPS

View file

@ -1,4 +1,4 @@
# GXXE01, GXXP01, GXXJ01 - POKeMON XD
# GXXE01, GXXP01, GXXJ01 - Pokemon XD: Gale of Darkness
[Core]
# Prevents crash when Greevil's henchman and Zook show up
@ -18,5 +18,5 @@ MMU = True
SafeTextureCacheColorSamples = 0
# Many areas of the game have unused vertexes, especially with cutscenes
# involving Shadow Pokémon, such as the purification cutscene.
# CPU Cull ends up greatly boosting performance for these cases.
# CPU Cull ends up greatly boosting performance for these cases.
CPUCull = True

View file

@ -12,17 +12,13 @@ $Allow Memory Card saving with Savestates
[Patches_RetroAchievements_Verified]
$Allow Memory Card saving with Savestates
[Gecko]
[ActionReplay]
$16:9 Widescreen
042EB168 00001783
042EB16C 000017EA
0400F614 60000000
0405C984 60000000
040875BC 60000000
0405C8AC 60000000
04086A34 60000000
04086930 60000000
044ED860 3FC962F9
$60 FPS
042AF894 38000000
042AEBEC 38000000
[Gecko_RetroAchievements_Verified]
[AR_RetroAchievements_Verified]
$16:9 Widescreen
$60 FPS

View file

@ -11,3 +11,14 @@ $Allow Memory Card saving with Savestates
[Patches_RetroAchievements_Verified]
$Allow Memory Card saving with Savestates
[ActionReplay]
$16:9 Widescreen
044CACE8 3FC962F9
$60 FPS
042AA2BC 38000000
042A9614 38000000
[AR_RetroAchievements_Verified]
$16:9 Widescreen
$60 FPS

View file

@ -12,13 +12,17 @@ $Allow Memory Card saving with Savestates
[Patches_RetroAchievements_Verified]
$Allow Memory Card saving with Savestates
[Gecko]
[ActionReplay]
$16:9 Widescreen
04005300 C3A2BFBC
04005304 EFBD00B2
04005308 480AF7A8
040B4AAC 4BF50854
0452A35C 3FAAAAAB
$60 FPS
042B17F8 38000000
042B0B50 38000000
[Gecko_RetroAchievements_Verified]
[AR_RetroAchievements_Verified]
$16:9 Widescreen
$60 FPS

View file

@ -0,0 +1,5 @@
# SP4PJW - Pétanque Master
[Video_Hacks]
# Immediate XFB causes seizure-inducing flickering
ImmediateXFBEnable = False

View file

@ -7,7 +7,7 @@ plugins {
@Suppress("UnstableApiUsage")
android {
compileSdkVersion = "android-34"
compileSdkVersion = "android-36"
ndkVersion = "27.0.12077973"
buildFeatures {
@ -40,7 +40,7 @@ android {
defaultConfig {
applicationId = "org.dolphinemu.dolphinemu"
minSdk = 21
targetSdk = 34
targetSdk = 36
versionCode = getBuildVersionCode()

View file

@ -362,7 +362,7 @@ public final class NativeLibrary
/**
* Pauses emulation.
*/
public static native void PauseEmulation();
public static native void PauseEmulation(boolean overrideAchievementRestrictions);
/**
* Stops emulation.

View file

@ -455,7 +455,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
MENU_ACTION_REFRESH_WIIMOTES -> NativeLibrary.RefreshWiimotes()
MENU_ACTION_PAUSE_EMULATION -> {
hasUserPausedEmulation = true
NativeLibrary.PauseEmulation()
NativeLibrary.PauseEmulation(false)
}
MENU_ACTION_UNPAUSE_EMULATION -> {

View file

@ -109,7 +109,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onPause() {
if (NativeLibrary.IsRunningAndUnpaused() && !NativeLibrary.IsShowingAlertMessage()) {
Log.debug("[EmulationFragment] Pausing emulation.")
NativeLibrary.PauseEmulation()
NativeLibrary.PauseEmulation(true)
}
super.onPause()
}

View file

@ -1,9 +1,9 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.9.0" apply false
id("com.android.library") version "8.9.0" apply false
id("com.android.application") version "8.11.0" apply false
id("com.android.library") version "8.11.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
id("com.android.test") version "8.9.0" apply false
id("com.android.test") version "8.11.0" apply false
id("androidx.baselineprofile") version "1.3.3" apply false
}

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View file

@ -271,10 +271,12 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_UnPauseEmula
Core::SetState(Core::System::GetInstance(), Core::State::Running);
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_PauseEmulation(JNIEnv*, jclass)
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_PauseEmulation(
JNIEnv*, jclass, bool override_achievement_restrictions)
{
HostThreadLock guard;
Core::SetState(Core::System::GetInstance(), Core::State::Paused);
Core::SetState(Core::System::GetInstance(), Core::State::Paused, true,
override_achievement_restrictions);
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulation(JNIEnv*, jclass)

View file

@ -356,14 +356,13 @@ void GekkoIRPlugin::OnCloseParen(ParenType type)
void GekkoIRPlugin::OnLabelDecl(std::string_view name)
{
const std::string name_str(name);
if (m_symset.contains(name_str))
if (const bool inserted = m_symset.insert(name_str).second; !inserted)
{
m_owner->EmitErrorHere(fmt::format("Label/Constant {} is already defined", name));
return;
}
m_labels[name_str] = m_active_block->BlockEndAddress();
m_symset.insert(name_str);
}
void GekkoIRPlugin::OnNumericLabelDecl(std::string_view, u32 num)
@ -374,14 +373,13 @@ void GekkoIRPlugin::OnNumericLabelDecl(std::string_view, u32 num)
void GekkoIRPlugin::OnVarDecl(std::string_view name)
{
const std::string name_str(name);
if (m_symset.contains(name_str))
if (const bool inserted = m_symset.insert(name_str).second; !inserted)
{
m_owner->EmitErrorHere(fmt::format("Label/Constant {} is already defined", name));
return;
}
m_active_var = &m_constants[name_str];
m_symset.insert(name_str);
}
void GekkoIRPlugin::PostParseAction()

View file

@ -5,6 +5,7 @@
#include <cstring>
#include <map>
#include <mutex>
#include <string>
#include <utility>
@ -34,6 +35,7 @@ void Symbol::Rename(const std::string& symbol_name)
void SymbolDB::List()
{
std::lock_guard lock(m_mutex);
for (const auto& func : m_functions)
{
DEBUG_LOG_FMT(OSHLE, "{} @ {:08x}: {} bytes (hash {:08x}) : {} calls", func.second.name,
@ -44,11 +46,14 @@ void SymbolDB::List()
bool SymbolDB::IsEmpty() const
{
std::lock_guard lock(m_mutex);
return m_functions.empty() && m_notes.empty();
}
bool SymbolDB::Clear(const char* prefix)
{
std::lock_guard lock(m_mutex);
// TODO: honor prefix
m_map_name.clear();
if (IsEmpty())
@ -61,16 +66,24 @@ bool SymbolDB::Clear(const char* prefix)
}
void SymbolDB::Index()
{
std::lock_guard lock(m_mutex);
Index(&m_functions);
}
void SymbolDB::Index(XFuncMap* functions)
{
int i = 0;
for (auto& func : m_functions)
for (auto& func : *functions)
{
func.second.index = i++;
}
}
Symbol* SymbolDB::GetSymbolFromName(std::string_view name)
const Symbol* SymbolDB::GetSymbolFromName(std::string_view name) const
{
std::lock_guard lock(m_mutex);
for (auto& func : m_functions)
{
if (func.second.function_name == name)
@ -80,9 +93,10 @@ Symbol* SymbolDB::GetSymbolFromName(std::string_view name)
return nullptr;
}
std::vector<Symbol*> SymbolDB::GetSymbolsFromName(std::string_view name)
std::vector<const Symbol*> SymbolDB::GetSymbolsFromName(std::string_view name) const
{
std::vector<Symbol*> symbols;
std::lock_guard lock(m_mutex);
std::vector<const Symbol*> symbols;
for (auto& func : m_functions)
{
@ -93,8 +107,10 @@ std::vector<Symbol*> SymbolDB::GetSymbolsFromName(std::string_view name)
return symbols;
}
Symbol* SymbolDB::GetSymbolFromHash(u32 hash)
const Symbol* SymbolDB::GetSymbolFromHash(u32 hash) const
{
std::lock_guard lock(m_mutex);
auto iter = m_checksum_to_function.find(hash);
if (iter == m_checksum_to_function.end())
return nullptr;
@ -102,8 +118,10 @@ Symbol* SymbolDB::GetSymbolFromHash(u32 hash)
return *iter->second.begin();
}
std::vector<Symbol*> SymbolDB::GetSymbolsFromHash(u32 hash)
std::vector<const Symbol*> SymbolDB::GetSymbolsFromHash(u32 hash) const
{
std::lock_guard lock(m_mutex);
const auto iter = m_checksum_to_function.find(hash);
if (iter == m_checksum_to_function.cend())
@ -114,6 +132,34 @@ std::vector<Symbol*> SymbolDB::GetSymbolsFromHash(u32 hash)
void SymbolDB::AddCompleteSymbol(const Symbol& symbol)
{
m_functions.emplace(symbol.address, symbol);
std::lock_guard lock(m_mutex);
m_functions[symbol.address] = symbol;
}
bool SymbolDB::RenameSymbol(const Symbol& symbol, const std::string& symbol_name)
{
std::lock_guard lock(m_mutex);
auto it = m_functions.find(symbol.address);
if (it == m_functions.end())
return false;
it->second.Rename(symbol_name);
return true;
}
bool SymbolDB::RenameSymbol(const Symbol& symbol, const std::string& symbol_name,
const std::string& object_name)
{
std::lock_guard lock(m_mutex);
auto it = m_functions.find(symbol.address);
if (it == m_functions.end())
return false;
it->second.Rename(symbol_name);
it->second.object_name = object_name;
return true;
}
} // namespace Common

View file

@ -7,12 +7,14 @@
#pragma once
#include <map>
#include <mutex>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "Common/Assert.h"
#include "Common/CommonTypes.h"
namespace Core
@ -87,27 +89,71 @@ public:
SymbolDB();
virtual ~SymbolDB();
virtual Symbol* GetSymbolFromAddr(u32 addr) { return nullptr; }
virtual Symbol* AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr) { return nullptr; }
virtual const Symbol* GetSymbolFromAddr(u32 addr) const { return nullptr; }
virtual const Symbol* AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr)
{
return nullptr;
}
void AddCompleteSymbol(const Symbol& symbol);
bool RenameSymbol(const Symbol& symbol, const std::string& symbol_name);
bool RenameSymbol(const Symbol& symbol, const std::string& symbol_name,
const std::string& object_name);
Symbol* GetSymbolFromName(std::string_view name);
std::vector<Symbol*> GetSymbolsFromName(std::string_view name);
Symbol* GetSymbolFromHash(u32 hash);
std::vector<Symbol*> GetSymbolsFromHash(u32 hash);
const Symbol* GetSymbolFromName(std::string_view name) const;
std::vector<const Symbol*> GetSymbolsFromName(std::string_view name) const;
const Symbol* GetSymbolFromHash(u32 hash) const;
std::vector<const Symbol*> GetSymbolsFromHash(u32 hash) const;
template <typename F>
void ForEachSymbol(F f) const
{
std::lock_guard lock(m_mutex);
for (const auto& [addr, symbol] : m_functions)
f(symbol);
}
template <typename F>
void ForEachSymbolWithMutation(F f)
{
std::lock_guard lock(m_mutex);
for (auto& [addr, symbol] : m_functions)
{
f(symbol);
ASSERT_MSG(COMMON, addr == symbol.address, "Symbol address was unexpectedly changed");
}
}
template <typename F>
void ForEachNote(F f) const
{
std::lock_guard lock(m_mutex);
for (const auto& [addr, note] : m_notes)
f(note);
}
template <typename F>
void ForEachNoteWithMutation(F f)
{
std::lock_guard lock(m_mutex);
for (auto& [addr, note] : m_notes)
{
f(note);
ASSERT_MSG(COMMON, addr == note.address, "Note address was unexpectedly changed");
}
}
const XFuncMap& Symbols() const { return m_functions; }
const XNoteMap& Notes() const { return m_notes; }
XFuncMap& AccessSymbols() { return m_functions; }
bool IsEmpty() const;
bool Clear(const char* prefix = "");
void List();
void Index();
protected:
static void Index(XFuncMap* functions);
XFuncMap m_functions;
XNoteMap m_notes;
XFuncPtrMap m_checksum_to_function;
std::string m_map_name;
mutable std::recursive_mutex m_mutex;
};
} // namespace Common

View file

@ -192,8 +192,8 @@ void AchievementManager::LoadGame(const DiscIO::Volume* volume)
std::lock_guard lg{m_lock};
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
const auto& names = volume->GetLongNames();
if (names.contains(DiscIO::Language::English))
m_title_estimate = names.at(DiscIO::Language::English);
if (const auto it = names.find(DiscIO::Language::English); it != names.end())
m_title_estimate = it->second;
else if (!names.empty())
m_title_estimate = names.begin()->second;
else

View file

@ -89,8 +89,8 @@ public:
static constexpr std::string_view BLUE = "#0B71C1";
static constexpr std::string_view APPROVED_LIST_FILENAME = "ApprovedInis.json";
static const inline Common::SHA1::Digest APPROVED_LIST_HASH = {
0x6D, 0x91, 0xF5, 0xC1, 0xE2, 0x4C, 0xC3, 0x39, 0xF5, 0x7F,
0xEC, 0xA9, 0x8C, 0xA9, 0xBD, 0x61, 0x28, 0x54, 0x11, 0x62};
0x29, 0x4C, 0xBD, 0x08, 0xF0, 0x5F, 0x47, 0x94, 0xC9, 0xB8,
0x05, 0x2E, 0x5C, 0xD6, 0x14, 0x48, 0xFA, 0x07, 0xE8, 0x53};
struct LeaderboardEntry
{

View file

@ -643,8 +643,10 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
SetDisc(system.GetDVDInterface(), DiscIO::CreateDisc(ipl.disc->path),
ipl.disc->auto_disc_change_paths);
}
AchievementManager::GetInstance().LoadGame(nullptr);
else
{
AchievementManager::GetInstance().LoadGame(nullptr);
}
SConfig::OnTitleDirectlyBooted(guard);
return true;

View file

@ -680,7 +680,7 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
// Set or get the running state
void SetState(Core::System& system, State state, bool report_state_change,
bool initial_execution_state)
bool override_achievement_restrictions)
{
// State cannot be controlled until the CPU Thread is operational
if (s_state.load() != State::Running)
@ -690,7 +690,7 @@ void SetState(Core::System& system, State state, bool report_state_change,
{
case State::Paused:
#ifdef USE_RETRO_ACHIEVEMENTS
if (!initial_execution_state && !AchievementManager::GetInstance().CanPause())
if (!override_achievement_restrictions && !AchievementManager::GetInstance().CanPause())
return;
#endif // USE_RETRO_ACHIEVEMENTS
// NOTE: GetState() will return State::Paused immediately, even before anything has

View file

@ -146,7 +146,7 @@ bool WantsDeterminism();
// [NOT THREADSAFE] For use by Host only
void SetState(Core::System& system, State state, bool report_state_change = true,
bool initial_execution_state = false);
bool override_achievement_restrictions = false);
State GetState(Core::System& system);
void SaveScreenShot();

View file

@ -6,7 +6,6 @@
#include <cstddef>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "Common/CommonTypes.h"
@ -104,7 +103,7 @@ public:
{
return 0xFFFFFFFF;
}
virtual std::string_view GetDescription(u32 /*address*/) const = 0;
virtual std::string GetDescription(u32 /*address*/) const = 0;
virtual void Clear(const CPUThreadGuard& guard) = 0;
};
} // namespace Core

View file

@ -80,7 +80,7 @@ bool GetCallstack(const Core::CPUThreadGuard& guard, std::vector<CallstackEntry>
});
WalkTheStack(guard, [&output, &ppc_symbol_db](u32 func_addr) {
std::string_view func_desc = ppc_symbol_db.GetDescription(func_addr);
std::string func_desc = ppc_symbol_db.GetDescription(func_addr);
if (func_desc.empty() || func_desc == "Invalid")
func_desc = "(unknown)";
@ -107,14 +107,14 @@ void PrintCallstack(const Core::CPUThreadGuard& guard, Common::Log::LogType type
GENERIC_LOG_FMT(type, level, " LR = 0 - this is bad");
}
if (const std::string_view lr_desc = ppc_symbol_db.GetDescription(LR(ppc_state));
if (const std::string lr_desc = ppc_symbol_db.GetDescription(LR(ppc_state));
lr_desc != ppc_symbol_db.GetDescription(ppc_state.pc))
{
GENERIC_LOG_FMT(type, level, " * {} [ LR = {:08x} ]", lr_desc, LR(ppc_state));
}
WalkTheStack(guard, [type, level, &ppc_symbol_db](u32 func_addr) {
std::string_view func_desc = ppc_symbol_db.GetDescription(func_addr);
std::string func_desc = ppc_symbol_db.GetDescription(func_addr);
if (func_desc.empty() || func_desc == "Invalid")
func_desc = "(unknown)";
GENERIC_LOG_FMT(type, level, " * {} [ addr = {:08x} ]", func_desc, func_addr);

View file

@ -439,7 +439,7 @@ u32 PPCDebugInterface::GetColor(const Core::CPUThreadGuard* guard, u32 address)
}
// =============
std::string_view PPCDebugInterface::GetDescription(u32 address) const
std::string PPCDebugInterface::GetDescription(u32 address) const
{
return m_ppc_symbol_db.GetDescription(address);
}

View file

@ -102,7 +102,7 @@ public:
void Step() override {}
void RunTo(u32 address) override;
u32 GetColor(const Core::CPUThreadGuard* guard, u32 address) const override;
std::string_view GetDescription(u32 address) const override;
std::string GetDescription(u32 address) const override;
std::shared_ptr<Core::NetworkCaptureLogger> NetworkLogger();

View file

@ -381,7 +381,7 @@ void RSOView::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symbol_db) c
u32 address = GetExportAddress(rso_export);
if (address != 0)
{
Common::Symbol* symbol = symbol_db->AddFunction(guard, address);
const Common::Symbol* symbol = symbol_db->AddFunction(guard, address);
if (!symbol)
symbol = symbol_db->GetSymbolFromAddr(address);
@ -389,8 +389,7 @@ void RSOView::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symbol_db) c
if (symbol)
{
// Function symbol
symbol->Rename(export_name);
symbol->object_name = rso_name;
symbol_db->RenameSymbol(*symbol, export_name, rso_name);
}
else
{

View file

@ -269,7 +269,7 @@ private:
u8 revision_id = 0; // 0xf0
u8 interrupt_mask = 0;
u8 interrupt = 0;
std::atomic<u8> interrupt = 0;
u16 device_id = 0xD107;
u8 acstart = 0x4E;
u32 hash_challenge = 0;

View file

@ -65,31 +65,11 @@ void Nunchuk::BuildDesiredExtensionState(DesiredExtensionState* target_state)
DataFormat nc_data = {};
// stick
bool override_occurred = false;
const ControllerEmu::AnalogStick::StateData stick_state =
m_stick->GetState(m_input_override_function, &override_occurred);
m_stick->GetState(m_input_override_function);
nc_data.jx = MapFloat<u8>(stick_state.x, STICK_CENTER, 0, STICK_RANGE);
nc_data.jy = MapFloat<u8>(stick_state.y, STICK_CENTER, 0, STICK_RANGE);
if (!override_occurred)
{
// Some terribly coded games check whether to move with a check like
//
// if (x != 0 && y != 0)
// do_movement(x, y);
//
// With keyboard controls, these games break if you simply hit one
// of the axes. Adjust this if you're hitting one of the axes so that
// we slightly tweak the other axis.
if (nc_data.jx != STICK_CENTER || nc_data.jy != STICK_CENTER)
{
if (nc_data.jx == STICK_CENTER)
++nc_data.jx;
if (nc_data.jy == STICK_CENTER)
++nc_data.jy;
}
}
// buttons
u8 buttons = 0;
m_buttons->GetState(&buttons, nunchuk_button_bitmasks.data(), m_input_override_function);

View file

@ -964,8 +964,9 @@ s32 WiiSockMan::NewSocket(s32 af, s32 type, s32 protocol)
s32 WiiSockMan::GetHostSocket(s32 wii_fd) const
{
if (WiiSockets.contains(wii_fd))
return WiiSockets.at(wii_fd).fd;
auto socket_entry = WiiSockets.find(wii_fd);
if (socket_entry != WiiSockets.end())
return socket_entry->second.fd;
return -EBADF;
}

View file

@ -186,9 +186,10 @@ FigureData SkylanderFigure::GetData() const
auto filter = std::make_pair(figure_data.figure_id, figure_data.variant_id);
Type type = Type::Item;
if (IOS::HLE::USB::list_skylanders.contains(filter))
if (const auto it = IOS::HLE::USB::list_skylanders.find(filter);
it != IOS::HLE::USB::list_skylanders.end())
{
auto found = IOS::HLE::USB::list_skylanders.at(filter);
auto found = it->second;
type = found.type;
}

View file

@ -176,9 +176,9 @@ std::optional<IPCReply> OH0::RegisterRemovalHook(const u64 device_id, const IOCt
{
std::lock_guard lock{m_hooks_mutex};
// IOS only allows a single device removal hook.
if (m_removal_hooks.contains(device_id))
const bool inserted = m_removal_hooks.try_emplace(device_id, request.address).second;
if (!inserted)
return IPCReply(IPC_EEXIST);
m_removal_hooks.insert({device_id, request.address});
return std::nullopt;
}

View file

@ -171,16 +171,20 @@ void USB_HIDv4::OnDeviceChange(ChangeEvent event, std::shared_ptr<USB::Device> d
std::lock_guard id_map_lock{m_id_map_mutex};
if (event == ChangeEvent::Inserted)
{
const auto id = device->GetId();
s32 new_id = 0;
while (m_ios_ids.contains(new_id))
while (!m_ios_ids.emplace(new_id, id).second)
++new_id;
m_ios_ids[new_id] = device->GetId();
m_device_ids[device->GetId()] = new_id;
m_device_ids[id] = new_id;
}
else if (event == ChangeEvent::Removed && m_device_ids.contains(device->GetId()))
else if (event == ChangeEvent::Removed)
{
m_ios_ids.erase(m_device_ids.at(device->GetId()));
m_device_ids.erase(device->GetId());
if (const auto it = m_device_ids.find(device->GetId()); it != m_device_ids.end())
{
m_ios_ids.erase(it->second);
m_device_ids.erase(it);
}
}
}

View file

@ -10,8 +10,8 @@
#include <libusb.h>
#endif
#include "Common/Assert.h"
#include "Common/Flag.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/Thread.h"
@ -24,9 +24,11 @@ public:
Impl()
{
const int ret = libusb_init(&m_context);
ASSERT_MSG(IOS_USB, ret == LIBUSB_SUCCESS, "Failed to init libusb: {}", ErrorWrap(ret));
if (ret != LIBUSB_SUCCESS)
{
ERROR_LOG_FMT(IOS_USB, "Failed to init libusb: {}", ErrorWrap(ret));
return;
}
#ifdef _WIN32
const int usbdk_ret = libusb_set_option(m_context, LIBUSB_OPTION_USE_USBDK);

View file

@ -2392,8 +2392,8 @@ void NetPlayClient::RequestGolfControl()
std::string NetPlayClient::GetCurrentGolfer()
{
std::lock_guard lkp(m_crit.players);
if (m_players.contains(m_current_golfer))
return m_players[m_current_golfer].name;
if (const auto it = m_players.find(m_current_golfer); it != m_players.end())
return it->second.name;
return "";
}

View file

@ -290,8 +290,8 @@ void NetPlayServer::ThreadFunc()
auto& e = m_async_queue.Front();
if (e.target_mode == TargetMode::Only)
{
if (m_players.contains(e.target_pid))
Send(m_players.at(e.target_pid).socket, e.packet, e.channel_id);
if (const auto it = m_players.find(e.target_pid); it != m_players.end())
Send(it->second.socket, e.packet, e.channel_id);
}
else
{
@ -794,9 +794,10 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
u32 cid;
packet >> cid;
if (m_chunked_data_complete_count.contains(cid))
if (const auto it = m_chunked_data_complete_count.find(cid);
it != m_chunked_data_complete_count.end())
{
m_chunked_data_complete_count[cid]++;
it->second++;
m_chunked_data_complete_event.Set();
}
}
@ -839,8 +840,11 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
if (m_host_input_authority)
{
// Prevent crash before game stop if the golfer disconnects
if (m_current_golfer != 0 && m_players.contains(m_current_golfer))
Send(m_players.at(m_current_golfer).socket, spac);
if (m_current_golfer != 0)
{
if (const auto it = m_players.find(m_current_golfer); it != m_players.end())
Send(it->second.socket, spac);
}
}
else
{

View file

@ -252,6 +252,7 @@ MemChecks::TMemChecksStr MemChecks::GetStrings() const
void MemChecks::AddFromStrings(const TMemChecksStr& mc_strings)
{
const Core::CPUThreadGuard guard(m_system);
DelayedMemCheckUpdate delayed_update(this);
for (const std::string& mc_string : mc_strings)
{
@ -279,13 +280,11 @@ void MemChecks::AddFromStrings(const TMemChecksStr& mc_strings)
mc.condition = Expression::TryParse(condition);
}
Add(std::move(mc), false);
delayed_update |= Add(std::move(mc));
}
Update();
}
void MemChecks::Add(TMemCheck memory_check, bool update)
DelayedMemCheckUpdate MemChecks::Add(TMemCheck memory_check)
{
const Core::CPUThreadGuard guard(m_system);
@ -304,8 +303,7 @@ void MemChecks::Add(TMemCheck memory_check, bool update)
m_mem_checks.emplace_back(std::move(memory_check));
}
if (update)
Update();
return DelayedMemCheckUpdate(this, true);
}
bool MemChecks::ToggleEnable(u32 address)
@ -319,20 +317,17 @@ bool MemChecks::ToggleEnable(u32 address)
return true;
}
bool MemChecks::Remove(u32 address, bool update)
DelayedMemCheckUpdate MemChecks::Remove(u32 address)
{
const auto iter = std::ranges::find(m_mem_checks, address, &TMemCheck::start_address);
if (iter == m_mem_checks.cend())
return false;
return DelayedMemCheckUpdate(this, false);
const Core::CPUThreadGuard guard(m_system);
m_mem_checks.erase(iter);
if (update)
Update();
return true;
return DelayedMemCheckUpdate(this, true);
}
void MemChecks::Clear()

View file

@ -97,6 +97,8 @@ private:
Core::System& m_system;
};
class DelayedMemCheckUpdate;
// Memory breakpoints
class MemChecks
{
@ -115,13 +117,13 @@ public:
TMemChecksStr GetStrings() const;
void AddFromStrings(const TMemChecksStr& mc_strings);
void Add(TMemCheck memory_check, bool update = true);
DelayedMemCheckUpdate Add(TMemCheck memory_check);
bool ToggleEnable(u32 address);
TMemCheck* GetMemCheck(u32 address, size_t size = 1);
bool OverlapsMemcheck(u32 address, u32 length) const;
bool Remove(u32 address, bool update = true);
DelayedMemCheckUpdate Remove(u32 address);
void Update();
void Clear();
@ -132,3 +134,39 @@ private:
Core::System& m_system;
bool m_mem_breakpoints_set = false;
};
class DelayedMemCheckUpdate final
{
public:
DelayedMemCheckUpdate(MemChecks* memchecks, bool update_needed = false)
: m_memchecks(memchecks), m_update_needed(update_needed)
{
}
DelayedMemCheckUpdate(const DelayedMemCheckUpdate&) = delete;
DelayedMemCheckUpdate(DelayedMemCheckUpdate&& other) = delete;
DelayedMemCheckUpdate& operator=(const DelayedMemCheckUpdate&) = delete;
DelayedMemCheckUpdate& operator=(DelayedMemCheckUpdate&& other) = delete;
~DelayedMemCheckUpdate()
{
if (m_update_needed)
m_memchecks->Update();
}
DelayedMemCheckUpdate& operator|=(DelayedMemCheckUpdate&& other)
{
if (m_memchecks == other.m_memchecks)
{
m_update_needed |= other.m_update_needed;
other.m_update_needed = false;
}
return *this;
}
operator bool() const { return m_update_needed; }
private:
MemChecks* m_memchecks;
bool m_update_needed;
};

View file

@ -172,12 +172,12 @@ static void RemoveBreakpoint(BreakpointType type, u32 addr, u32 len)
else
{
auto& memchecks = Core::System::GetInstance().GetPowerPC().GetMemChecks();
DelayedMemCheckUpdate delayed_update(&memchecks);
while (memchecks.GetMemCheck(addr, len) != nullptr)
{
memchecks.Remove(addr, false);
delayed_update |= memchecks.Remove(addr);
INFO_LOG_FMT(GDB_STUB, "gdb: removed a memcheck: {:08x} bytes at {:08x}", len, addr);
}
memchecks.Update();
}
Host_PPCBreakpointsChanged();
}

View file

@ -40,9 +40,10 @@ const void* ConstantPool::GetConstant(const void* value, size_t element_size, si
size_t index)
{
const size_t value_size = element_size * num_elements;
auto iter = m_const_info.find(value);
const auto [iter, inserted] = m_const_info.emplace(value, ConstantInfo{});
ConstantInfo& info = iter->second;
if (iter == m_const_info.end())
if (inserted)
{
void* ptr = std::align(ALIGNMENT, value_size, m_current_ptr, m_remaining_size);
ASSERT_MSG(DYNA_REC, ptr, "Constant pool has run out of space.");
@ -51,10 +52,9 @@ const void* ConstantPool::GetConstant(const void* value, size_t element_size, si
m_remaining_size -= value_size;
std::memcpy(ptr, value, value_size);
iter = m_const_info.emplace(std::make_pair(value, ConstantInfo{ptr, value_size})).first;
info = ConstantInfo{ptr, value_size};
}
const ConstantInfo& info = iter->second;
ASSERT_MSG(DYNA_REC, info.m_size == value_size, "Constant has incorrect size in constant pool.");
u8* location = static_cast<u8*>(info.m_location);
return location + element_size * index;

View file

@ -187,7 +187,7 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link,
LinkBlock(block);
}
Common::Symbol* symbol = nullptr;
const Common::Symbol* symbol = nullptr;
if (Common::JitRegister::IsEnabled() &&
(symbol = m_jit.m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)) != nullptr)
{

View file

@ -346,10 +346,10 @@ static void FindFunctionsFromHandlers(const Core::CPUThreadGuard& guard, PPCSymb
if (read_result.valid && PPCTables::IsValidInstruction(read_result.hex, entry.first))
{
// Check if this function is already mapped
Common::Symbol* f = func_db->AddFunction(guard, entry.first);
const Common::Symbol* f = func_db->AddFunction(guard, entry.first);
if (!f)
continue;
f->Rename(entry.second);
func_db->RenameSymbol(*f, entry.second);
}
}
}
@ -359,8 +359,8 @@ static void FindFunctionsAfterReturnInstruction(const Core::CPUThreadGuard& guar
{
std::vector<u32> funcAddrs;
for (const auto& func : func_db->Symbols())
funcAddrs.push_back(func.second.address + func.second.size);
func_db->ForEachSymbol(
[&](const Common::Symbol& symbol) { funcAddrs.push_back(symbol.address + symbol.size); });
auto& mmu = guard.GetSystem().GetMMU();
for (u32& location : funcAddrs)
@ -380,7 +380,7 @@ static void FindFunctionsAfterReturnInstruction(const Core::CPUThreadGuard& guar
if (read_result.valid && PPCTables::IsValidInstruction(read_result.hex, location))
{
// check if this function is already mapped
Common::Symbol* f = func_db->AddFunction(guard, location);
const Common::Symbol* f = func_db->AddFunction(guard, location);
if (!f)
break;
else
@ -407,45 +407,45 @@ void FindFunctions(const Core::CPUThreadGuard& guard, u32 startAddr, u32 endAddr
int numLeafs = 0, numNice = 0, numUnNice = 0;
int numTimer = 0, numRFI = 0, numStraightLeaf = 0;
int leafSize = 0, niceSize = 0, unniceSize = 0;
for (auto& func : func_db->AccessSymbols())
{
if (func.second.address == 4)
func_db->ForEachSymbolWithMutation([&](Common::Symbol& f) {
if (f.address == 4)
{
WARN_LOG_FMT(SYMBOLS, "Weird function");
continue;
}
AnalyzeFunction2(func_db, &(func.second));
Common::Symbol& f = func.second;
if (f.name.substr(0, 3) == "zzz")
{
if (f.flags & Common::FFLAG_LEAF)
f.Rename(f.name + "_leaf");
if (f.flags & Common::FFLAG_STRAIGHT)
f.Rename(f.name + "_straight");
}
if (f.flags & Common::FFLAG_LEAF)
{
numLeafs++;
leafSize += f.size;
}
else if (f.flags & Common::FFLAG_ONLYCALLSNICELEAFS)
{
numNice++;
niceSize += f.size;
}
else
{
numUnNice++;
unniceSize += f.size;
}
AnalyzeFunction2(func_db, &f);
if (f.name.substr(0, 3) == "zzz")
{
if (f.flags & Common::FFLAG_LEAF)
f.Rename(f.name + "_leaf");
if (f.flags & Common::FFLAG_STRAIGHT)
f.Rename(f.name + "_straight");
}
if (f.flags & Common::FFLAG_LEAF)
{
numLeafs++;
leafSize += f.size;
}
else if (f.flags & Common::FFLAG_ONLYCALLSNICELEAFS)
{
numNice++;
niceSize += f.size;
}
else
{
numUnNice++;
unniceSize += f.size;
}
if (f.flags & Common::FFLAG_TIMERINSTRUCTIONS)
numTimer++;
if (f.flags & Common::FFLAG_RFI)
numRFI++;
if ((f.flags & Common::FFLAG_STRAIGHT) && (f.flags & Common::FFLAG_LEAF))
numStraightLeaf++;
}
if (f.flags & Common::FFLAG_TIMERINSTRUCTIONS)
numTimer++;
if (f.flags & Common::FFLAG_RFI)
numRFI++;
if ((f.flags & Common::FFLAG_STRAIGHT) && (f.flags & Common::FFLAG_LEAF))
numStraightLeaf++;
}
});
if (numLeafs == 0)
leafSize = 0;
else

View file

@ -35,8 +35,10 @@ PPCSymbolDB::PPCSymbolDB() = default;
PPCSymbolDB::~PPCSymbolDB() = default;
// Adds the function to the list, unless it's already there
Common::Symbol* PPCSymbolDB::AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr)
const Common::Symbol* PPCSymbolDB::AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr)
{
std::lock_guard lock(m_mutex);
// It's already in the list
if (m_functions.contains(start_addr))
return nullptr;
@ -56,8 +58,18 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd
const std::string& name, const std::string& object_name,
Common::Symbol::Type type)
{
auto iter = m_functions.find(startAddr);
if (iter != m_functions.end())
std::lock_guard lock(m_mutex);
AddKnownSymbol(guard, startAddr, size, name, object_name, type, &m_functions,
&m_checksum_to_function);
}
void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAddr, u32 size,
const std::string& name, const std::string& object_name,
Common::Symbol::Type type, XFuncMap* functions,
XFuncPtrMap* checksum_to_function)
{
auto iter = functions->find(startAddr);
if (iter != functions->end())
{
// already got it, let's just update name, checksum & size to be sure.
Common::Symbol* tempfunc = &iter->second;
@ -70,7 +82,7 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd
else
{
// new symbol. run analyze.
auto& new_symbol = m_functions.emplace(startAddr, name).first->second;
auto& new_symbol = functions->emplace(startAddr, name).first->second;
new_symbol.object_name = object_name;
new_symbol.type = type;
new_symbol.address = startAddr;
@ -85,7 +97,7 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd
name, size, new_symbol.size);
new_symbol.size = size;
}
m_checksum_to_function[new_symbol.hash].insert(&new_symbol);
(*checksum_to_function)[new_symbol.hash].insert(&new_symbol);
}
else
{
@ -96,9 +108,15 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd
void PPCSymbolDB::AddKnownNote(u32 start_addr, u32 size, const std::string& name)
{
auto iter = m_notes.find(start_addr);
std::lock_guard lock(m_mutex);
AddKnownNote(start_addr, size, name, &m_notes);
}
if (iter != m_notes.end())
void PPCSymbolDB::AddKnownNote(u32 start_addr, u32 size, const std::string& name, XNoteMap* notes)
{
auto iter = notes->find(start_addr);
if (iter != notes->end())
{
// Already got it, just update the name and size.
Common::Note* tempfunc = &iter->second;
@ -112,35 +130,38 @@ void PPCSymbolDB::AddKnownNote(u32 start_addr, u32 size, const std::string& name
tf.address = start_addr;
tf.size = size;
m_notes[start_addr] = tf;
(*notes)[start_addr] = tf;
}
}
void PPCSymbolDB::DetermineNoteLayers()
{
if (m_notes.empty())
std::lock_guard lock(m_mutex);
DetermineNoteLayers(&m_notes);
}
void PPCSymbolDB::DetermineNoteLayers(XNoteMap* notes)
{
if (notes->empty())
return;
for (auto& note : m_notes)
for (auto& note : *notes)
note.second.layer = 0;
for (auto iter = m_notes.begin(); iter != m_notes.end(); ++iter)
for (auto iter = notes->begin(); iter != notes->end(); ++iter)
{
const u32 range = iter->second.address + iter->second.size;
auto search = m_notes.lower_bound(range);
auto search = notes->lower_bound(range);
while (--search != iter)
search->second.layer += 1;
}
}
Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr)
const Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) const
{
// If m_functions is changing, there should be a PPCSymbolsChanged signal afterward. The signal
// will re-update persistent symbol displays by calling this function. Only one-off calls to this
// function, such as printing the symbol to console, should be affected by leaving early.
std::unique_lock<std::mutex> lock(m_write_lock, std::try_to_lock);
if (!lock.owns_lock() || m_functions.empty())
std::lock_guard lock(m_mutex);
if (m_functions.empty())
return nullptr;
auto it = m_functions.lower_bound(addr);
@ -162,10 +183,10 @@ Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr)
return nullptr;
}
Common::Note* PPCSymbolDB::GetNoteFromAddr(u32 addr)
const Common::Note* PPCSymbolDB::GetNoteFromAddr(u32 addr) const
{
std::unique_lock<std::mutex> lock(m_write_lock, std::try_to_lock);
if (!lock.owns_lock() || m_notes.empty())
std::lock_guard lock(m_mutex);
if (m_notes.empty())
return nullptr;
auto itn = m_notes.lower_bound(addr);
@ -195,15 +216,17 @@ Common::Note* PPCSymbolDB::GetNoteFromAddr(u32 addr)
void PPCSymbolDB::DeleteFunction(u32 start_address)
{
std::lock_guard lock(m_mutex);
m_functions.erase(start_address);
}
void PPCSymbolDB::DeleteNote(u32 start_address)
{
std::lock_guard lock(m_mutex);
m_notes.erase(start_address);
}
std::string_view PPCSymbolDB::GetDescription(u32 addr)
std::string PPCSymbolDB::GetDescription(u32 addr) const
{
if (const Common::Symbol* const symbol = GetSymbolFromAddr(addr))
return symbol->name;
@ -212,9 +235,7 @@ std::string_view PPCSymbolDB::GetDescription(u32 addr)
void PPCSymbolDB::FillInCallers()
{
std::unique_lock<std::mutex> lock(m_write_lock, std::try_to_lock);
if (!lock.owns_lock())
return;
std::lock_guard lock(m_mutex);
for (auto& p : m_functions)
{
@ -247,6 +268,8 @@ void PPCSymbolDB::FillInCallers()
void PPCSymbolDB::PrintCalls(u32 funcAddr) const
{
std::lock_guard lock(m_mutex);
const auto iter = m_functions.find(funcAddr);
if (iter == m_functions.end())
{
@ -268,6 +291,8 @@ void PPCSymbolDB::PrintCalls(u32 funcAddr) const
void PPCSymbolDB::PrintCallers(u32 funcAddr) const
{
std::lock_guard lock(m_mutex);
const auto iter = m_functions.find(funcAddr);
if (iter == m_functions.end())
return;
@ -286,6 +311,8 @@ void PPCSymbolDB::PrintCallers(u32 funcAddr) const
void PPCSymbolDB::LogFunctionCall(u32 addr)
{
std::lock_guard lock(m_mutex);
auto iter = m_functions.find(addr);
if (iter == m_functions.end())
return;
@ -314,25 +341,22 @@ bool PPCSymbolDB::FindMapFile(std::string* existing_map_file, std::string* writa
return false;
}
// Returns true if m_functions was changed.
bool PPCSymbolDB::LoadMapOnBoot(const Core::CPUThreadGuard& guard)
{
// Loads from emuthread and can crash with main thread accessing the map. Any other loads will be
// done on the main thread and should be safe. Returns true if m_functions was changed.
std::lock_guard lock(m_write_lock);
std::string existing_map_file;
if (!PPCSymbolDB::FindMapFile(&existing_map_file, nullptr))
return Clear();
// If the map is already loaded (such as restarting the same game), skip reloading.
if (!IsEmpty() && existing_map_file == m_map_name)
return false;
{
std::lock_guard lock(m_mutex);
// If the map is already loaded (such as restarting the same game), skip reloading.
if (!IsEmpty() && existing_map_file == m_map_name)
return false;
}
// Load map into cleared m_functions.
bool changed = Clear();
if (!LoadMap(guard, existing_map_file))
return changed;
if (!LoadMap(guard, std::move(existing_map_file)))
return Clear();
return true;
}
@ -344,14 +368,11 @@ bool PPCSymbolDB::LoadMapOnBoot(const Core::CPUThreadGuard& guard)
// function names and addresses that have a BLR before the start and at the end, but ignore any that
// don't, and then tell you how many were good and how many it ignored. That way you either find out
// it is all good and use it, find out it is partly good and use the good part, or find out that
// only
// a handful of functions lined up by coincidence and then you can clear the symbols. In the future
// I
// want to make it smarter, so it checks that there are no BLRs in the middle of the function
// (by checking the code length), and also make it cope with added functions in the middle or work
// based on the order of the functions and their approximate length. Currently that process has to
// be
// done manually and is very tedious.
// only a handful of functions lined up by coincidence and then you can clear the symbols. In the
// future I want to make it smarter, so it checks that there are no BLRs in the middle of the
// function (by checking the code length), and also make it cope with added functions in the middle
// or work based on the order of the functions and their approximate length. Currently that process
// has to be done manually and is very tedious.
// The use case for separate handling of map files that aren't bad is that you usually want to also
// load names that aren't functions(if included in the map file) without them being rejected as
// invalid.
@ -362,12 +383,16 @@ bool PPCSymbolDB::LoadMapOnBoot(const Core::CPUThreadGuard& guard)
// This one can load both leftover map files on game discs (like Zelda), and mapfiles
// produced by SaveSymbolMap below.
// bad=true means carefully load map files that might not be from exactly the right version
bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string& filename, bool bad)
bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, std::string filename, bool bad)
{
File::IOFile f(filename, "r");
if (!f)
return false;
XFuncMap new_functions;
XNoteMap new_notes;
XFuncPtrMap checksum_to_function;
// Two columns are used by Super Smash Bros. Brawl Korean map file
// Three columns are commonly used
// Four columns are used in American Mensa Academy map files and perhaps other games
@ -579,9 +604,14 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string&
++good_count;
if (section_name == ".note")
AddKnownNote(vaddress, size, name);
{
AddKnownNote(vaddress, size, name, &new_notes);
}
else
AddKnownSymbol(guard, vaddress, size, name_string, object_filename_string, type);
{
AddKnownSymbol(guard, vaddress, size, name_string, object_filename_string, type,
&new_functions, &checksum_to_function);
}
}
else
{
@ -590,10 +620,16 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string&
}
}
m_map_name = filename;
Index(&new_functions);
DetermineNoteLayers(&new_notes);
FillInCallers();
std::lock_guard lock(m_mutex);
std::swap(m_functions, new_functions);
std::swap(m_notes, new_notes);
std::swap(m_checksum_to_function, checksum_to_function);
std::swap(m_map_name, filename);
Index();
DetermineNoteLayers();
NOTICE_LOG_FMT(SYMBOLS, "{} symbols loaded, {} symbols ignored.", good_count, bad_count);
return true;
}
@ -605,6 +641,8 @@ bool PPCSymbolDB::SaveSymbolMap(const std::string& filename) const
if (!file)
return false;
std::lock_guard lock(m_mutex);
// Write .text section
auto function_symbols =
m_functions |
@ -655,7 +693,7 @@ bool PPCSymbolDB::SaveSymbolMap(const std::string& filename) const
return true;
}
// Save code map (won't work if Core is running)
// Save code map
//
// Notes:
// - Dolphin doesn't load back code maps
@ -670,6 +708,8 @@ bool PPCSymbolDB::SaveCodeMap(const Core::CPUThreadGuard& guard, const std::stri
// Write ".text" at the top
f.WriteString(".text\n");
std::lock_guard lock(m_mutex);
const auto& ppc_debug_interface = guard.GetSystem().GetPowerPC().GetDebugInterface();
u32 next_address = 0;

View file

@ -3,7 +3,6 @@
#pragma once
#include <mutex>
#include <string>
#include <string_view>
@ -22,25 +21,25 @@ public:
PPCSymbolDB();
~PPCSymbolDB() override;
Common::Symbol* AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr) override;
const Common::Symbol* AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr) override;
void AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAddr, u32 size,
const std::string& name, const std::string& object_name,
Common::Symbol::Type type = Common::Symbol::Type::Function);
void AddKnownNote(u32 start_addr, u32 size, const std::string& name);
Common::Symbol* GetSymbolFromAddr(u32 addr) override;
const Common::Symbol* GetSymbolFromAddr(u32 addr) const override;
bool NoteExists() const { return !m_notes.empty(); }
Common::Note* GetNoteFromAddr(u32 addr);
const Common::Note* GetNoteFromAddr(u32 addr) const;
void DetermineNoteLayers();
void DeleteFunction(u32 start_address);
void DeleteNote(u32 start_address);
std::string_view GetDescription(u32 addr);
std::string GetDescription(u32 addr) const;
void FillInCallers();
bool LoadMapOnBoot(const Core::CPUThreadGuard& guard);
bool LoadMap(const Core::CPUThreadGuard& guard, const std::string& filename, bool bad = false);
bool LoadMap(const Core::CPUThreadGuard& guard, std::string filename, bool bad = false);
bool SaveSymbolMap(const std::string& filename) const;
bool SaveCodeMap(const Core::CPUThreadGuard& guard, const std::string& filename) const;
@ -51,5 +50,11 @@ public:
static bool FindMapFile(std::string* existing_map_file, std::string* writable_map_file);
private:
std::mutex m_write_lock;
static void AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAddr, u32 size,
const std::string& name, const std::string& object_name,
Common::Symbol::Type type, XFuncMap* functions,
XFuncPtrMap* checksum_to_function);
static void AddKnownNote(u32 start_addr, u32 size, const std::string& name, XNoteMap* notes);
static void DetermineNoteLayers(XNoteMap* notes);
};

View file

@ -160,20 +160,18 @@ bool MEGASignatureDB::Save(const std::string& file_path) const
void MEGASignatureDB::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symbol_db) const
{
for (auto& it : symbol_db->AccessSymbols())
{
auto& symbol = it.second;
symbol_db->ForEachSymbol([&](const Common::Symbol& symbol) {
for (const auto& sig : m_signatures)
{
if (Compare(guard, symbol.address, symbol.size, sig))
{
symbol.name = sig.name;
symbol_db->RenameSymbol(symbol, sig.name);
INFO_LOG_FMT(SYMBOLS, "Found {} at {:08x} (size: {:08x})!", sig.name, symbol.address,
symbol.size);
break;
}
}
}
});
symbol_db->Index();
}

View file

@ -128,7 +128,7 @@ void HashSignatureDB::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symb
for (const auto& function : symbol_db->GetSymbolsFromHash(entry.first))
{
// Found the function. Let's rename it according to the symbol file.
function->Rename(entry.second.name);
symbol_db->RenameSymbol(*function, entry.second.name);
if (entry.second.size == static_cast<unsigned int>(function->size))
{
INFO_LOG_FMT(SYMBOLS, "Found {} at {:08x} (size: {:08x})!", entry.second.name,
@ -146,18 +146,17 @@ void HashSignatureDB::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symb
void HashSignatureDB::Populate(const PPCSymbolDB* symbol_db, const std::string& filter)
{
for (const auto& symbol : symbol_db->Symbols())
{
if ((filter.empty() && (!symbol.second.name.empty()) &&
symbol.second.name.substr(0, 3) != "zz_" && symbol.second.name.substr(0, 1) != ".") ||
((!filter.empty()) && symbol.second.name.substr(0, filter.size()) == filter))
symbol_db->ForEachSymbol([&](const Common::Symbol& symbol) {
if ((filter.empty() && (!symbol.name.empty()) && symbol.name.substr(0, 3) != "zz_" &&
symbol.name.substr(0, 1) != ".") ||
((!filter.empty()) && symbol.name.substr(0, filter.size()) == filter))
{
DBFunc temp_dbfunc;
temp_dbfunc.name = symbol.second.name;
temp_dbfunc.size = symbol.second.size;
m_database[symbol.second.hash] = temp_dbfunc;
temp_dbfunc.name = symbol.name;
temp_dbfunc.size = symbol.size;
m_database[symbol.hash] = temp_dbfunc;
}
}
});
}
u32 HashSignatureDB::ComputeCodeChecksum(const Core::CPUThreadGuard& guard, u32 offsetStart,

View file

@ -727,12 +727,12 @@ static bool ValidateHeaders(const StateHeader& header)
std::string loaded_str = header.version_string;
const u32 loaded_version = header.version_header.version_cookie - COOKIE_BASE;
if (s_old_versions.contains(loaded_version))
if (const auto it = s_old_versions.find(loaded_version); it != s_old_versions.end())
{
// This is a REALLY old version, before we started writing the version string to file
success = false;
std::pair<std::string, std::string> version_range = s_old_versions.find(loaded_version)->second;
std::pair<std::string, std::string> version_range = it->second;
std::string oldest_version = version_range.first;
std::string newest_version = version_range.second;

View file

@ -112,14 +112,16 @@ void GameConfigWidget::CreateWidgets()
m_deterministic_dual_core =
new ConfigStringChoice(choice, Config::MAIN_GPU_DETERMINISM_MODE, layer);
m_enable_mmu->setToolTip(tr(
m_enable_mmu->SetDescription(tr(
"Enables the Memory Management Unit, needed for some games. (ON = Compatible, OFF = Fast)"));
m_enable_fprf->setToolTip(tr("Enables Floating Point Result Flag calculation, needed for a few "
"games. (ON = Compatible, OFF = Fast)"));
m_sync_gpu->setToolTip(tr("Synchronizes the GPU and CPU threads to help prevent random freezes "
"in Dual core mode. (ON = Compatible, OFF = Fast)"));
m_emulate_disc_speed->setToolTip(
m_enable_fprf->SetDescription(
tr("Enables Floating Point Result Flag calculation, needed for a few "
"games. (ON = Compatible, OFF = Fast)"));
m_sync_gpu->SetDescription(
tr("Synchronizes the GPU and CPU threads to help prevent random freezes "
"in Dual core mode. (ON = Compatible, OFF = Fast)"));
m_emulate_disc_speed->SetDescription(
tr("Enable emulated disc speed. Disabling this can cause crashes "
"and other problems in some games. "
"(ON = Compatible, OFF = Unlocked)"));
@ -143,11 +145,11 @@ void GameConfigWidget::CreateWidgets()
m_use_monoscopic_shadows =
new ConfigBool(tr("Monoscopic Shadows"), Config::GFX_STEREO_EFB_MONO_DEPTH, layer);
m_depth_slider->setToolTip(
m_depth_slider->SetDescription(
tr("This value is multiplied with the depth set in the graphics configuration."));
m_convergence_spin->setToolTip(
m_convergence_spin->SetDescription(
tr("This value is added to the convergence value set in the graphics configuration."));
m_use_monoscopic_shadows->setToolTip(
m_use_monoscopic_shadows->SetDescription(
tr("Use a single depth buffer for both eyes. Needed for a few games."));
stereoscopy_layout->addWidget(new ConfigSliderLabel(tr("Depth Percentage:"), m_depth_slider), 0,

View file

@ -335,7 +335,7 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard)
std::string ins = (split == std::string::npos ? disas : disas.substr(0, split));
std::string param = (split == std::string::npos ? "" : disas.substr(split + 1));
const std::string_view desc = debug_interface.GetDescription(addr);
const std::string desc = debug_interface.GetDescription(addr);
const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(addr);
std::string note_string;
@ -433,8 +433,6 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard)
CalculateBranchIndentation();
m_ppc_symbol_db.FillInCallers();
repaint();
m_updating = false;
}
@ -950,7 +948,7 @@ void CodeViewWidget::OnFollowBranch()
void CodeViewWidget::OnEditSymbol()
{
const u32 addr = GetContextAddress();
Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr);
const Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr);
if (symbol == nullptr)
{
@ -974,12 +972,14 @@ void CodeViewWidget::OnEditSymbol()
}
if (symbol->name != name)
symbol->Rename(name);
m_ppc_symbol_db.RenameSymbol(*symbol, name);
if (symbol->size != size)
{
Core::CPUThreadGuard guard(m_system);
PPCAnalyst::ReanalyzeFunction(guard, symbol->address, *symbol, size);
Common::Symbol new_symbol = *symbol;
PPCAnalyst::ReanalyzeFunction(guard, symbol->address, new_symbol, size);
m_ppc_symbol_db.AddCompleteSymbol(new_symbol);
}
emit Host::GetInstance()->PPCSymbolsChanged();
@ -988,7 +988,7 @@ void CodeViewWidget::OnEditSymbol()
void CodeViewWidget::OnDeleteSymbol()
{
const u32 addr = GetContextAddress();
Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr);
const Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr);
if (symbol == nullptr)
return;
@ -1039,7 +1039,7 @@ void CodeViewWidget::OnSelectionChanged()
void CodeViewWidget::OnEditNote()
{
const u32 context_address = GetContextAddress();
Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address);
const Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address);
if (note == nullptr)
return;
@ -1071,7 +1071,7 @@ void CodeViewWidget::OnEditNote()
void CodeViewWidget::OnDeleteNote()
{
const u32 context_address = GetContextAddress();
Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address);
const Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address);
if (note == nullptr)
return;

View file

@ -423,14 +423,13 @@ void CodeWidget::UpdateSymbols()
m_symbols_list->selectedItems()[0]->text();
m_symbols_list->clear();
for (const auto& symbol : m_ppc_symbol_db.Symbols())
{
QString name = QString::fromStdString(symbol.second.name);
m_ppc_symbol_db.ForEachSymbol([&](const Common::Symbol& symbol) {
QString name = QString::fromStdString(symbol.name);
// If the symbol has an object name, add it to the entry name.
if (!symbol.second.object_name.empty())
if (!symbol.object_name.empty())
{
name += QString::fromStdString(fmt::format(" ({})", symbol.second.object_name));
name += QString::fromStdString(fmt::format(" ({})", symbol.object_name));
}
auto* item = new QListWidgetItem(name);
@ -438,14 +437,14 @@ void CodeWidget::UpdateSymbols()
item->setSelected(true);
// Disable non-function symbols as you can't do anything with them.
if (symbol.second.type != Common::Symbol::Type::Function)
if (symbol.type != Common::Symbol::Type::Function)
item->setFlags(Qt::NoItemFlags);
item->setData(Qt::UserRole, symbol.second.address);
item->setData(Qt::UserRole, symbol.address);
if (name.contains(m_symbol_filter, Qt::CaseInsensitive))
m_symbols_list->addItem(item);
}
});
m_symbols_list->sortItems();
}
@ -457,19 +456,18 @@ void CodeWidget::UpdateNotes()
m_note_list->selectedItems()[0]->text();
m_note_list->clear();
for (const auto& note : m_ppc_symbol_db.Notes())
{
const QString name = QString::fromStdString(note.second.name);
m_ppc_symbol_db.ForEachNote([&](const Common::Note& note) {
const QString name = QString::fromStdString(note.name);
auto* item = new QListWidgetItem(name);
if (name == selection)
item->setSelected(true);
item->setData(Qt::UserRole, note.second.address);
item->setData(Qt::UserRole, note.address);
if (name.toUpper().indexOf(m_symbol_filter.toUpper()) != -1)
m_note_list->addItem(item);
}
});
m_note_list->sortItems();
}

View file

@ -29,8 +29,10 @@
#include "Core/Core.h"
#include "Core/HW/AddressSpace.h"
#include "Core/PowerPC/BreakPoints.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "DolphinQt/Debugger/EditSymbolDialog.h"
#include "DolphinQt/Host.h"
#include "DolphinQt/Resources.h"
#include "DolphinQt/Settings.h"
@ -196,7 +198,7 @@ private:
};
MemoryViewWidget::MemoryViewWidget(Core::System& system, QWidget* parent)
: QWidget(parent), m_system(system)
: QWidget(parent), m_system(system), m_ppc_symbol_db(m_system.GetPPCSymbolDB())
{
auto* layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
@ -220,6 +222,8 @@ MemoryViewWidget::MemoryViewWidget(Core::System& system, QWidget* parent)
this->setLayout(layout);
connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &MemoryViewWidget::UpdateFont);
connect(Host::GetInstance(), &Host::PPCSymbolsChanged, this,
[this] { UpdateDispatcher(UpdateType::Symbols); });
connect(Host::GetInstance(), &Host::PPCBreakpointsChanged, this,
&MemoryViewWidget::UpdateBreakpointTags);
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] {
@ -347,6 +351,9 @@ void MemoryViewWidget::UpdateDispatcher(UpdateType type)
if (Core::GetState(m_system) == Core::State::Paused)
GetValues();
UpdateColumns();
[[fallthrough]];
case UpdateType::Symbols:
UpdateSymbols();
break;
case UpdateType::Auto:
// Values were captured on CPU thread while doing a callback.
@ -371,7 +378,7 @@ void MemoryViewWidget::CreateTable()
// Span is the number of unique memory values covered in one row.
const int data_span = m_bytes_per_row / GetTypeSize(m_type);
m_data_columns = m_dual_view ? data_span * 2 : data_span;
const int total_columns = MISC_COLUMNS + m_data_columns;
const int total_columns = MISC_COLUMNS + m_data_columns + (m_show_symbols ? 1 : 0);
const int rows =
std::round((m_table->height() / static_cast<float>(m_table->rowHeight(0))) - 0.25);
@ -440,6 +447,15 @@ void MemoryViewWidget::CreateTable()
m_table->setItem(i, c + MISC_COLUMNS, item.clone());
}
if (!m_show_symbols)
continue;
// Symbols
auto* description_item = new QTableWidgetItem(QStringLiteral("-"));
description_item->setFlags(Qt::ItemIsEnabled);
m_table->setItem(i, m_table->columnCount() - 1, description_item);
}
// Update column width
@ -500,6 +516,9 @@ void MemoryViewWidget::Update()
item->setBackground(Qt::transparent);
item->setData(USER_ROLE_VALID_ADDRESS, false);
}
if (m_show_symbols)
m_table->item(i, m_table->columnCount() - 1)->setData(USER_ROLE_CELL_ADDRESS, row_address);
}
UpdateBreakpointTags();
@ -576,6 +595,34 @@ void MemoryViewWidget::UpdateColumns()
}
}
void MemoryViewWidget::UpdateSymbols()
{
if (!m_show_symbols)
return;
// Update symbols
for (int i = 0; i < m_table->rowCount(); i++)
{
auto* item = m_table->item(i, m_table->columnCount() - 1);
if (!item)
continue;
const u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt();
const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(address);
std::string desc;
if (note == nullptr)
desc = m_ppc_symbol_db.GetDescription(address);
else
desc = note->name;
item->setText(QString::fromStdString(" " + desc));
}
if (m_show_symbols)
m_table->resizeColumnToContents(m_table->columnCount() - 1);
}
// Always runs on CPU thread from a callback.
void MemoryViewWidget::UpdateOnFrameEnd()
{
@ -1010,6 +1057,7 @@ void MemoryViewWidget::ToggleBreakpoint(u32 addr, bool row)
{
const Core::CPUThreadGuard guard(m_system);
DelayedMemCheckUpdate delayed_update(&memchecks);
for (int i = 0; i < breaks; i++)
{
@ -1028,16 +1076,14 @@ void MemoryViewWidget::ToggleBreakpoint(u32 addr, bool row)
check.log_on_hit = m_do_log;
check.break_on_hit = true;
memchecks.Add(std::move(check), false);
delayed_update |= memchecks.Add(std::move(check));
}
else if (check_ptr != nullptr)
{
// Using the pointer fixes misaligned breakpoints (0x11 breakpoint in 0x10 aligned view).
memchecks.Remove(check_ptr->start_address, false);
delayed_update |= memchecks.Remove(check_ptr->start_address);
}
}
memchecks.Update();
}
emit Host::GetInstance()->PPCBreakpointsChanged();
@ -1059,6 +1105,78 @@ void MemoryViewWidget::OnCopyHex(u32 addr)
QStringLiteral("%1").arg(value, sizeof(u64) * 2, 16, QLatin1Char('0')).left(length * 2));
}
void MemoryViewWidget::ShowSymbols(bool enable)
{
m_show_symbols = enable;
UpdateDispatcher(UpdateType::Full);
}
void MemoryViewWidget::OnEditSymbol(EditSymbolType type, u32 addr)
{
// Add Note and Add Region use these values.
std::string name = "";
std::string object_name = "";
u32 size = GetTypeSize(m_type);
u32 address = addr;
EditSymbolDialog::Type dialog_type = EditSymbolDialog::Type::Note;
// Add and edit region are tied to the same context menu action.
if (type == EditSymbolType::EditRegion)
{
// If symbol doesn't exist, it's safe to add a new region.
const Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr);
dialog_type = EditSymbolDialog::Type::Symbol;
if (symbol != nullptr)
{
// Leave the more specialized function editing to code widget.
if (symbol->type != Common::Symbol::Type::Data)
return;
// Edit data region.
name = symbol->name;
object_name = symbol->object_name;
size = symbol->size;
address = symbol->address;
}
}
else if (type == EditSymbolType::EditNote)
{
const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(addr);
if (note == nullptr)
return;
name = note->name;
size = note->size;
address = note->address;
}
EditSymbolDialog dialog(this, address, &size, &name, dialog_type);
if (dialog.exec() != QDialog::Accepted)
return;
if (dialog.DeleteRequested())
{
if (type == EditSymbolType::EditRegion)
m_ppc_symbol_db.DeleteFunction(address);
else
m_ppc_symbol_db.DeleteNote(address);
}
else if (type == EditSymbolType::EditRegion)
{
m_ppc_symbol_db.AddKnownSymbol(Core::CPUThreadGuard{m_system}, address, size, name, object_name,
Common::Symbol::Type::Data);
}
else
{
m_ppc_symbol_db.AddKnownNote(address, size, name);
m_ppc_symbol_db.DetermineNoteLayers();
}
emit Host::GetInstance()->PPCSymbolsChanged();
}
void MemoryViewWidget::OnContextMenu(const QPoint& pos)
{
auto* item_selected = m_table->itemAt(pos);
@ -1089,6 +1207,21 @@ void MemoryViewWidget::OnContextMenu(const QPoint& pos)
menu->addSeparator();
auto* note_add_action = menu->addAction(
tr("Add Note"), this, [this, addr] { OnEditSymbol(EditSymbolType::AddNote, addr); });
auto* note_edit_action = menu->addAction(
tr("Edit Note"), this, [this, addr] { OnEditSymbol(EditSymbolType::EditNote, addr); });
menu->addAction(tr("Add or edit region label"), this,
[this, addr] { OnEditSymbol(EditSymbolType::EditRegion, addr); });
auto* note = m_ppc_symbol_db.GetNoteFromAddr(addr);
note_edit_action->setEnabled(note != nullptr);
// A note cannot be added ontop of the starting address of another note.
if (note != nullptr && note->address == addr)
note_add_action->setEnabled(false);
menu->addSeparator();
menu->addAction(tr("Show in code"), this, [this, addr] { emit ShowCode(addr); });
menu->addSeparator();

View file

@ -24,6 +24,8 @@ class CPUThreadGuard;
class System;
} // namespace Core
class PPCSymbolDB;
// Captures direct editing of the table.
class TableEditDelegate : public QStyledItemDelegate
{
@ -76,14 +78,23 @@ public:
Full,
Addresses,
Values,
Symbols,
Auto,
};
enum class EditSymbolType
{
AddNote,
EditNote,
EditRegion,
};
explicit MemoryViewWidget(Core::System& system, QWidget* parent = nullptr);
void CreateTable();
void UpdateDispatcher(UpdateType type = UpdateType::Addresses);
void Update();
void UpdateSymbols();
void UpdateOnFrameEnd();
void GetValues();
void UpdateFont(const QFont& font);
@ -98,6 +109,7 @@ public:
void SetBPType(BPType type);
void SetAddress(u32 address);
void SetFocus() const;
void ShowSymbols(bool enable);
void SetBPLoggingEnabled(bool enabled);
@ -108,6 +120,7 @@ signals:
void ActivateSearch();
private:
void OnEditSymbol(EditSymbolType type, u32 addr);
void OnContextMenu(const QPoint& pos);
void OnCopyAddress(u32 addr);
void OnCopyHex(u32 addr);
@ -116,9 +129,11 @@ private:
void UpdateColumns();
void ScrollbarActionTriggered(int action);
void ScrollbarSliderReleased();
std::optional<QString> ValueToString(const Core::CPUThreadGuard& guard, u32 address, Type type);
Core::System& m_system;
PPCSymbolDB& m_ppc_symbol_db;
MemoryViewTable* m_table;
QScrollBar* m_scrollbar;
@ -137,6 +152,7 @@ private:
int m_alignment = 16;
int m_data_columns;
bool m_dual_view = false;
bool m_show_symbols = true;
std::mutex m_updating;
QColor m_highlight_color = QColor(120, 255, 255, 100);

View file

@ -17,6 +17,7 @@
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QMenuBar>
#include <QPushButton>
#include <QRegularExpression>
@ -32,6 +33,7 @@
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/AddressSpace.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/System.h"
#include "DolphinQt/Debugger/MemoryViewWidget.h"
#include "DolphinQt/Host.h"
@ -41,7 +43,7 @@
using Type = MemoryViewWidget::Type;
MemoryWidget::MemoryWidget(Core::System& system, QWidget* parent)
: QDockWidget(parent), m_system(system)
: QDockWidget(parent), m_system(system), m_ppc_symbol_db(system.GetPPCSymbolDB())
{
setWindowTitle(tr("Memory"));
setObjectName(QStringLiteral("memory"));
@ -247,6 +249,25 @@ void MemoryWidget::CreateWidgets()
bp_layout->addWidget(m_bp_log_check);
bp_layout->setSpacing(1);
// Notes
m_labels_group = new QGroupBox(tr("Labels"));
auto* symbols_box = new QTabWidget;
m_note_list = new QListWidget;
m_data_list = new QListWidget;
m_symbols_list = new QListWidget;
symbols_box->addTab(m_note_list, tr("Notes"));
symbols_box->addTab(m_data_list, tr("Data"));
symbols_box->addTab(m_symbols_list, tr("Symbols"));
m_symbols_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
auto* labels_layout = new QVBoxLayout;
m_search_labels = new QLineEdit;
m_search_labels->setPlaceholderText(tr("Filter Label List"));
m_labels_group->setLayout(labels_layout);
labels_layout->addWidget(symbols_box);
labels_layout->addWidget(m_search_labels);
// Sidebar
auto* sidebar = new QWidget;
auto* sidebar_layout = new QVBoxLayout;
@ -262,8 +283,9 @@ void MemoryWidget::CreateWidgets()
&MemoryWidget::OnSetValueFromFile);
menubar->addMenu(menu_import);
// View Menu
auto* auto_update_action =
menu_views->addAction(tr("Auto update memory values"), this, [this](bool checked) {
menu_views->addAction(tr("&Auto update memory values"), this, [this](bool checked) {
m_auto_update_enabled = checked;
if (checked)
RegisterAfterFrameEventCallback();
@ -274,14 +296,23 @@ void MemoryWidget::CreateWidgets()
auto_update_action->setChecked(true);
auto* highlight_update_action =
menu_views->addAction(tr("Highlight recently changed values"), this,
menu_views->addAction(tr("&Highlight recently changed values"), this,
[this](bool checked) { m_memory_view->ToggleHighlights(checked); });
highlight_update_action->setCheckable(true);
highlight_update_action->setChecked(true);
menu_views->addAction(tr("Highlight color"), this,
menu_views->addAction(tr("Highlight &color"), this,
[this] { m_memory_view->SetHighlightColor(); });
auto* show_notes =
menu_views->addAction(tr("&Show symbols and notes"), this, [this](bool checked) {
m_labels_visible = checked;
m_memory_view->ShowSymbols(checked);
UpdateNotes();
});
show_notes->setCheckable(true);
show_notes->setChecked(true);
QMenu* menu_export = new QMenu(tr("&Export"), menubar);
menu_export->addAction(tr("Dump &MRAM"), this, &MemoryWidget::OnDumpMRAM);
menu_export->addAction(tr("Dump &ExRAM"), this, &MemoryWidget::OnDumpExRAM);
@ -306,6 +337,7 @@ void MemoryWidget::CreateWidgets()
sidebar_layout->addWidget(address_space_group);
sidebar_layout->addItem(new QSpacerItem(1, 10));
sidebar_layout->addWidget(bp_group);
sidebar_layout->addWidget(m_labels_group);
sidebar_layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding));
// Splitter
@ -327,6 +359,7 @@ void MemoryWidget::CreateWidgets()
auto* widget = new QWidget;
widget->setLayout(layout);
setWidget(widget);
UpdateNotes();
}
void MemoryWidget::ConnectWidgets()
@ -359,6 +392,12 @@ void MemoryWidget::ConnectWidgets()
connect(m_base_check, &QCheckBox::toggled, this, &MemoryWidget::ValidateAndPreviewInputValue);
connect(m_bp_log_check, &QCheckBox::toggled, this, &MemoryWidget::OnBPLogChanged);
for (auto* list : {m_symbols_list, m_data_list, m_note_list})
connect(list, &QListWidget::itemClicked, this, &MemoryWidget::OnSelectLabel);
connect(Host::GetInstance(), &Host::PPCSymbolsChanged, this, &MemoryWidget::RefreshLabelBox);
connect(m_search_labels, &QLineEdit::textChanged, this, &MemoryWidget::RefreshLabelBox);
connect(m_memory_view, &MemoryViewWidget::ShowCode, this, &MemoryWidget::ShowCode);
connect(m_memory_view, &MemoryViewWidget::RequestWatch, this, &MemoryWidget::RequestWatch);
connect(m_memory_view, &MemoryViewWidget::ActivateSearch, this,
@ -795,6 +834,98 @@ void MemoryWidget::OnSetValueFromFile()
Update();
}
void MemoryWidget::RefreshLabelBox()
{
if (!m_labels_visible || m_ppc_symbol_db.IsEmpty())
{
m_labels_group->hide();
return;
}
m_labels_group->show();
UpdateSymbols();
UpdateNotes();
}
void MemoryWidget::OnSelectLabel()
{
QList<QListWidgetItem*> items;
if (m_note_list->isVisible())
items = m_note_list->selectedItems();
else if (m_symbols_list->isVisible())
items = m_symbols_list->selectedItems();
else if (m_data_list->isVisible())
items = m_data_list->selectedItems();
if (items.isEmpty())
return;
const u32 address = items[0]->data(Qt::UserRole).toUInt();
SetAddress(address);
}
void MemoryWidget::UpdateSymbols()
{
const QString selection = m_symbols_list->selectedItems().isEmpty() ?
QString{} :
m_symbols_list->selectedItems()[0]->text();
m_symbols_list->clear();
m_data_list->clear();
m_ppc_symbol_db.ForEachSymbol([&](const Common::Symbol& symbol) {
QString name = QString::fromStdString(symbol.name);
// If the symbol has an object name, add it to the entry name.
if (!symbol.object_name.empty())
{
name += QString::fromStdString(fmt::format(" ({})", symbol.object_name));
}
auto* item = new QListWidgetItem(name);
if (name == selection)
item->setSelected(true);
item->setData(Qt::UserRole, symbol.address);
if (!name.contains(m_search_labels->text(), Qt::CaseInsensitive))
return;
if (symbol.type != Common::Symbol::Type::Function)
m_data_list->addItem(item);
else
m_symbols_list->addItem(item);
});
m_symbols_list->sortItems();
}
void MemoryWidget::UpdateNotes()
{
// Save selection to re-apply.
const QString selection = m_note_list->selectedItems().isEmpty() ?
QStringLiteral("") :
m_note_list->selectedItems()[0]->text();
m_note_list->clear();
m_ppc_symbol_db.ForEachNote([&](const Common::Note& note) {
const QString name = QString::fromStdString(note.name);
auto* item = new QListWidgetItem(name);
if (name == selection)
item->setSelected(true);
item->setData(Qt::UserRole, note.address);
// Filter notes based on the search text.
if (name.contains(m_search_labels->text(), Qt::CaseInsensitive))
m_note_list->addItem(item);
});
m_note_list->sortItems();
}
static void DumpArray(const std::string& filename, const u8* data, size_t length)
{
if (!data)

View file

@ -14,8 +14,11 @@
class MemoryViewWidget;
class QCheckBox;
class QComboBox;
class QGroupBox;
class QHideEvent;
class QLabel;
class QLineEdit;
class QListWidget;
class QPushButton;
class QRadioButton;
class QShowEvent;
@ -27,6 +30,8 @@ class System;
class CPUThreadGuard;
} // namespace Core
class PPCSymbolDB;
class MemoryWidget : public QDockWidget
{
Q_OBJECT
@ -66,6 +71,11 @@ private:
void OnSetValue();
void OnSetValueFromFile();
void OnSelectLabel();
void RefreshLabelBox();
void UpdateSymbols();
void UpdateNotes();
void OnDumpMRAM();
void OnDumpExRAM();
void OnDumpARAM();
@ -85,6 +95,7 @@ private:
void ActivateSearchAddress();
Core::System& m_system;
PPCSymbolDB& m_ppc_symbol_db;
MemoryViewWidget* m_memory_view;
QSplitter* m_splitter;
@ -115,6 +126,15 @@ private:
QRadioButton* m_bp_read_only;
QRadioButton* m_bp_write_only;
QCheckBox* m_bp_log_check;
QGroupBox* m_labels_group;
QLineEdit* m_search_labels;
QListWidget* m_symbols_list;
QListWidget* m_data_list;
QListWidget* m_note_list;
QString m_note_filter;
bool m_labels_visible = true;
Common::EventHook m_vi_end_field_event;
bool m_auto_update_enabled = true;

View file

@ -251,17 +251,18 @@ static std::unique_ptr<QDirIterator> GetIterator(const QString& dir)
void GameTracker::RemoveDirectoryInternal(const QString& dir)
{
RemovePath(dir);
auto it = GetIterator(dir);
while (it->hasNext())
const auto dir_it = GetIterator(dir);
while (dir_it->hasNext())
{
QString path = QFileInfo(it->next()).canonicalFilePath();
if (m_tracked_files.contains(path))
QString path = QFileInfo(dir_it->next()).canonicalFilePath();
if (const auto it = m_tracked_files.find(path); it != m_tracked_files.end())
{
m_tracked_files[path].remove(dir);
if (m_tracked_files[path].empty())
auto& set = *it;
set.remove(dir);
if (set.isEmpty())
{
RemovePath(path);
m_tracked_files.remove(path);
m_tracked_files.erase(it);
if (m_started)
emit GameRemoved(path.toStdString());
}
@ -271,16 +272,14 @@ void GameTracker::RemoveDirectoryInternal(const QString& dir)
void GameTracker::UpdateDirectoryInternal(const QString& dir)
{
auto it = GetIterator(dir);
while (it->hasNext() && !m_processing_halted)
const auto dir_it = GetIterator(dir);
while (dir_it->hasNext() && !m_processing_halted)
{
QString path = QFileInfo(it->next()).canonicalFilePath();
QString path = QFileInfo(dir_it->next()).canonicalFilePath();
if (m_tracked_files.contains(path))
if (const auto it = m_tracked_files.find(path); it != m_tracked_files.end())
{
auto& tracked_file = m_tracked_files[path];
if (!tracked_file.contains(dir))
tracked_file.insert(dir);
it->insert(dir);
}
else
{

View file

@ -656,8 +656,9 @@ void NetPlayDialog::UpdateGUI()
auto* name_item = new QTableWidgetItem(QString::fromStdString(p->name));
name_item->setToolTip(name_item->text());
const auto& status_info = player_status.contains(p->game_status) ?
player_status.at(p->game_status) :
const auto it = player_status.find(p->game_status);
const auto& status_info = it != player_status.end() ?
it->second :
std::make_pair(QStringLiteral("?"), QStringLiteral("?"));
auto* status_item = new QTableWidgetItem(status_info.first);
status_item->setToolTip(status_info.second);

View file

@ -175,40 +175,40 @@ void Settings::ApplyStyle()
}
#ifdef _WIN32
if (stylesheet_contents.isEmpty())
// Unlike other OSes we don't automatically get a default dark theme on Windows.
// We manually load a dark palette for our included "(Dark)" style,
// and for *any* external style when the system is in "Dark" mode.
// Unfortunately it doesn't seem trivial to load a palette based on the stylesheet itself.
if (style_type == StyleType::Dark || (style_type != StyleType::Light && IsSystemDark()))
{
// No theme selected or found. Usually we would just fallthrough and set an empty stylesheet
// which would select Qt's default theme, but unlike other OSes we don't automatically get a
// default dark theme on Windows when the user has selected dark mode in the Windows settings.
// So manually check if the user wants dark mode and, if yes, load our embedded dark theme.
if (style_type == StyleType::Dark || (style_type != StyleType::Light && IsSystemDark()))
if (stylesheet_contents.isEmpty())
{
QFile file(QStringLiteral(":/dolphin_dark_win/dark.qss"));
if (file.open(QFile::ReadOnly))
stylesheet_contents = QString::fromUtf8(file.readAll().data());
}
QPalette palette = qApp->style()->standardPalette();
palette.setColor(QPalette::Window, QColor(32, 32, 32));
palette.setColor(QPalette::WindowText, QColor(220, 220, 220));
palette.setColor(QPalette::Base, QColor(32, 32, 32));
palette.setColor(QPalette::AlternateBase, QColor(48, 48, 48));
palette.setColor(QPalette::PlaceholderText, QColor(126, 126, 126));
palette.setColor(QPalette::Text, QColor(220, 220, 220));
palette.setColor(QPalette::Button, QColor(48, 48, 48));
palette.setColor(QPalette::ButtonText, QColor(220, 220, 220));
palette.setColor(QPalette::BrightText, QColor(255, 255, 255));
palette.setColor(QPalette::Highlight, QColor(0, 120, 215));
palette.setColor(QPalette::HighlightedText, QColor(255, 255, 255));
palette.setColor(QPalette::Link, QColor(100, 160, 220));
palette.setColor(QPalette::LinkVisited, QColor(100, 160, 220));
qApp->setPalette(palette);
}
else
{
// reset any palette changes that may exist from a previously set dark mode
if (s_default_palette)
qApp->setPalette(*s_default_palette);
}
QPalette palette = qApp->style()->standardPalette();
palette.setColor(QPalette::Window, QColor(32, 32, 32));
palette.setColor(QPalette::WindowText, QColor(220, 220, 220));
palette.setColor(QPalette::Base, QColor(32, 32, 32));
palette.setColor(QPalette::AlternateBase, QColor(48, 48, 48));
palette.setColor(QPalette::PlaceholderText, QColor(126, 126, 126));
palette.setColor(QPalette::Text, QColor(220, 220, 220));
palette.setColor(QPalette::Button, QColor(48, 48, 48));
palette.setColor(QPalette::ButtonText, QColor(220, 220, 220));
palette.setColor(QPalette::BrightText, QColor(255, 255, 255));
palette.setColor(QPalette::Highlight, QColor(0, 120, 215));
palette.setColor(QPalette::HighlightedText, QColor(255, 255, 255));
palette.setColor(QPalette::Link, QColor(100, 160, 220));
palette.setColor(QPalette::LinkVisited, QColor(100, 160, 220));
qApp->setPalette(palette);
}
else
{
// reset any palette changes that may exist from a previously set dark mode
if (s_default_palette)
qApp->setPalette(*s_default_palette);
}
#endif

View file

@ -731,7 +731,7 @@ void GameCubePane::LoadSettings()
}
}
m_skip_main_menu->setEnabled(have_menu);
m_skip_main_menu->setEnabled(have_menu || !m_skip_main_menu->isChecked());
m_skip_main_menu->setToolTip(have_menu ? QString{} : tr("Put IPL ROMs in User/GC/<region>."));
// Device Settings

View file

@ -50,13 +50,6 @@ AnalogStick::StateData AnalogStick::GetState() const
}
AnalogStick::StateData AnalogStick::GetState(const InputOverrideFunction& override_func) const
{
bool override_occurred = false;
return GetState(override_func, &override_occurred);
}
AnalogStick::StateData AnalogStick::GetState(const InputOverrideFunction& override_func,
bool* override_occurred) const
{
StateData state = GetState();
if (!override_func)
@ -65,13 +58,11 @@ AnalogStick::StateData AnalogStick::GetState(const InputOverrideFunction& overri
if (const std::optional<ControlState> x_override = override_func(name, X_INPUT_OVERRIDE, state.x))
{
state.x = *x_override;
*override_occurred = true;
}
if (const std::optional<ControlState> y_override = override_func(name, Y_INPUT_OVERRIDE, state.y))
{
state.y = *y_override;
*override_occurred = true;
}
return state;

View file

@ -22,7 +22,6 @@ public:
StateData GetState() const;
StateData GetState(const InputOverrideFunction& override_func) const;
StateData GetState(const InputOverrideFunction& override_func, bool* override_occurred) const;
private:
Control* GetModifierInput() const override;

View file

@ -77,6 +77,8 @@ void Attachments::LoadConfig(Common::IniFile::Section* sec, const std::string& b
void Attachments::SaveConfig(Common::IniFile::Section* sec, const std::string& base)
{
ControlGroup::SaveConfig(sec, base);
if (GetSelectionSetting().IsSimpleValue())
{
sec->Set(base + name, GetAttachmentList()[GetSelectedAttachment()]->GetName(), "None");

View file

@ -10,8 +10,6 @@
#include <hidclass.h>
#include <mutex>
#include "Common/Flag.h"
#include "Common/Logging/Log.h"
#include "InputCommon/ControllerInterface/DInput/DInput.h"
@ -20,7 +18,6 @@
#pragma comment(lib, "OneCoreUAP.Lib")
static std::mutex s_populate_mutex;
// TODO is this really needed?
static Common::Flag s_first_populate_devices_asked;
static HCMNOTIFICATION s_notify_handle;
@ -52,7 +49,6 @@ _Pre_satisfies_(EventDataSize >= sizeof(CM_NOTIFY_EVENT_DATA)) static DWORD CALL
// listen for it.
if (s_first_populate_devices_asked.IsSet())
{
std::lock_guard lk_population(s_populate_mutex);
// TODO: we could easily use the message passed alongside this event, which tells
// whether a device was added or removed, to avoid removing old, still connected, devices
g_controller_interface.PlatformPopulateDevices([&] {
@ -96,17 +92,18 @@ InputBackend::InputBackend(ControllerInterface* controller_interface)
void InputBackend::PopulateDevices()
{
std::lock_guard lk_population(s_populate_mutex);
s_first_populate_devices_asked.Set();
ciface::DInput::PopulateDevices(GetHWND());
ciface::XInput::PopulateDevices();
ciface::WGInput::PopulateDevices();
g_controller_interface.PlatformPopulateDevices([this] {
s_first_populate_devices_asked.Set();
ciface::DInput::PopulateDevices(GetHWND());
ciface::XInput::PopulateDevices();
ciface::WGInput::PopulateDevices();
});
}
void InputBackend::HandleWindowChange()
{
std::lock_guard lk_population(s_populate_mutex);
ciface::DInput::ChangeWindow(GetHWND());
g_controller_interface.PlatformPopulateDevices(
[this] { ciface::DInput::ChangeWindow(GetHWND()); });
}
InputBackend::~InputBackend()

View file

@ -380,7 +380,7 @@ std::unique_ptr<VKPipeline> VKPipeline::Create(const AbstractPipelineConfig& con
static const VkRect2D scissor = {{0, 0}, {1, 1}};
static const VkPipelineViewportStateCreateInfo viewport_state = {
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
&depth_clamp_state,
g_backend_info.bSupportsUnrestrictedDepthRange ? &depth_clamp_state : nullptr,
0, // VkPipelineViewportStateCreateFlags flags;
1, // uint32_t viewportCount
&viewport, // const VkViewport* pViewports

View file

@ -664,7 +664,7 @@ bool VulkanContext::SelectDeviceExtensions(bool enable_surface)
AddExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false);
AddExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME, false);
if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_D32F_CLEAR))
if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DEPTH_CLAMP_CONTROL))
{
// Unrestricted depth range is one of the few extensions that changes the behavior
// of Vulkan just by being enabled, so we rely on lazy evaluation to ensure it is

View file

@ -158,6 +158,8 @@ constexpr BugInfo m_known_bugs[] = {
BUG_BROKEN_DYNAMIC_SAMPLER_INDEXING, -1.0, -1.0, true},
{API_VULKAN, OS_ANDROID, VENDOR_QUALCOMM, DRIVER_QUALCOMM, Family::UNKNOWN,
BUG_SLOW_OPTIMAL_IMAGE_TO_BUFFER_COPY, -1.0, -1.0, true},
{API_VULKAN, OS_ALL, VENDOR_ATI, DRIVER_ATI, Family::UNKNOWN, BUG_BROKEN_DEPTH_CLAMP_CONTROL,
-1.0, -1.0, true},
};
static std::map<Bug, BugInfo> m_bugs;
@ -297,6 +299,7 @@ static const char* to_string(Bug bug)
case BUG_BROKEN_DISCARD_WITH_EARLY_Z: return "broken-discard-with-early-z";
case BUG_BROKEN_DYNAMIC_SAMPLER_INDEXING: return "broken-dynamic-sampler-indexing";
case BUG_SLOW_OPTIMAL_IMAGE_TO_BUFFER_COPY: return "slow-optimal-image-to-buffer-copy";
case BUG_BROKEN_DEPTH_CLAMP_CONTROL: return "broken-depth-clamp-control";
}
return "Unknown";
}

View file

@ -342,7 +342,14 @@ enum Bug
// Affected devices: Adreno
// Started Version: -1
// Ended Version: -1
BUG_SLOW_OPTIMAL_IMAGE_TO_BUFFER_COPY
BUG_SLOW_OPTIMAL_IMAGE_TO_BUFFER_COPY,
// BUG: Incorrect implementation of VK_EXT_depth_clamp_control causes incorrect depth values to
// be written to the depth buffer.
// Affected devices: Official AMD (RADV is unaffected)
// Started Version: -1
// Ended Version: -1
BUG_BROKEN_DEPTH_CLAMP_CONTROL
};
// Initializes our internal vendor, device family, and driver version

View file

@ -197,14 +197,13 @@ void GraphicsModManager::Load(const GraphicsModGroupConfig& config)
{
for (const GraphicsTargetGroupConfig& group : mod.m_groups)
{
if (m_groups.contains(group.m_name))
if (const bool inserted = m_groups.insert(group.m_name).second; !inserted)
{
WARN_LOG_FMT(
VIDEO,
"Specified graphics mod group '{}' for mod '{}' is already specified by another mod.",
group.m_name, mod.m_title);
}
m_groups.insert(group.m_name);
const auto internal_group = fmt::format("{}.{}", mod.m_title, group.m_name);
for (const GraphicsTargetConfig& target : group.m_targets)

View file

@ -1535,26 +1535,21 @@ const AbstractPipeline* ShaderCache::GetTextureReinterpretPipeline(TextureFormat
TextureFormat to_format)
{
const auto key = std::make_pair(from_format, to_format);
auto iter = m_texture_reinterpret_pipelines.find(key);
if (iter != m_texture_reinterpret_pipelines.end())
const auto [iter, inserted] = m_texture_reinterpret_pipelines.emplace(key, nullptr);
if (!inserted)
return iter->second.get();
std::string shader_source =
FramebufferShaderGen::GenerateTextureReinterpretShader(from_format, to_format);
if (shader_source.empty())
{
m_texture_reinterpret_pipelines.emplace(key, nullptr);
return nullptr;
}
std::unique_ptr<AbstractShader> shader = g_gfx->CreateShaderFromSource(
ShaderStage::Pixel, shader_source,
fmt::format("Texture reinterpret pixel shader: {} to {}", from_format, to_format));
if (!shader)
{
m_texture_reinterpret_pipelines.emplace(key, nullptr);
return nullptr;
}
AbstractPipelineConfig config;
config.vertex_format = nullptr;
@ -1566,8 +1561,8 @@ const AbstractPipeline* ShaderCache::GetTextureReinterpretPipeline(TextureFormat
config.blending_state = RenderState::GetNoBlendingBlendState();
config.framebuffer_state = RenderState::GetRGBA8FramebufferState();
config.usage = AbstractPipelineUsage::Utility;
auto iiter = m_texture_reinterpret_pipelines.emplace(key, g_gfx->CreatePipeline(config));
return iiter.first->second.get();
iter->second = g_gfx->CreatePipeline(config);
return iter->second.get();
}
const AbstractShader*
@ -1576,17 +1571,14 @@ ShaderCache::GetTextureDecodingShader(TextureFormat format,
{
const auto key = std::make_pair(static_cast<u32>(format),
static_cast<u32>(palette_format.value_or(TLUTFormat::IA8)));
const auto iter = m_texture_decoding_shaders.find(key);
if (iter != m_texture_decoding_shaders.end())
const auto [iter, inserted] = m_texture_decoding_shaders.emplace(key, nullptr);
if (!inserted)
return iter->second.get();
const std::string shader_source =
TextureConversionShaderTiled::GenerateDecodingShader(format, palette_format, APIType::OpenGL);
if (shader_source.empty())
{
m_texture_decoding_shaders.emplace(key, nullptr);
return nullptr;
}
const std::string name =
palette_format.has_value() ?
@ -1596,12 +1588,9 @@ ShaderCache::GetTextureDecodingShader(TextureFormat format,
std::unique_ptr<AbstractShader> shader =
g_gfx->CreateShaderFromSource(ShaderStage::Compute, shader_source, name);
if (!shader)
{
m_texture_decoding_shaders.emplace(key, nullptr);
return nullptr;
}
const auto iiter = m_texture_decoding_shaders.emplace(key, std::move(shader));
return iiter.first->second.get();
iter->second = std::move(shader);
return iter->second.get();
}
} // namespace VideoCommon

View file

@ -652,8 +652,8 @@ void TextureCacheBase::DoSaveState(PointerWrap& p)
auto refpair1 = std::make_pair(*id1, *id2);
auto refpair2 = std::make_pair(*id2, *id1);
if (!reference_pairs.contains(refpair1) && !reference_pairs.contains(refpair2))
reference_pairs.insert(refpair1);
if (!reference_pairs.contains(refpair2))
reference_pairs.insert(std::move(refpair1));
}
}
@ -1029,6 +1029,11 @@ SamplerState TextureCacheBase::GetSamplerState(u32 index, float custom_tex_scale
// Anisotropic filtering option.
if (g_ActiveConfig.iMaxAnisotropy != AnisotropicFilteringMode::Default &&
IsAnisostropicEnhancementSafe(tm0))
{
state.tm0.anisotropic_filtering = Common::ToUnderlying(g_ActiveConfig.iMaxAnisotropy);
}
if (state.tm0.anisotropic_filtering != 0)
{
// https://www.opengl.org/registry/specs/EXT/texture_filter_anisotropic.txt
// For predictable results on all hardware/drivers, only use one of:
@ -1041,7 +1046,6 @@ SamplerState TextureCacheBase::GetSamplerState(u32 index, float custom_tex_scale
state.tm0.mag_filter = FilterMode::Linear;
if (tm0.mipmap_filter != MipMode::None)
state.tm0.mipmap_filter = FilterMode::Linear;
state.tm0.anisotropic_filtering = Common::ToUnderlying(g_ActiveConfig.iMaxAnisotropy);
}
if (has_arbitrary_mips && tm0.mipmap_filter != MipMode::None)

View file

@ -74,6 +74,28 @@ VertexShaderUid GetVertexShaderUid()
return out;
}
static void WritePrimitiveExpand(APIType api_type, const ShaderHostConfig& host_config,
const vertex_shader_uid_data* uid_data, ShaderCode& out)
{
if (uid_data->vs_expand == VSExpand::None)
return;
out.Write("InputData dolphin_primitive_expand_data(int index_offset)\n");
out.Write("{{\n");
if (api_type == APIType::D3D)
{
// D3D doesn't include the base vertex in SV_VertexID
// See comment in UberShaderVertex for details
out.Write("\tuint vertex_id = (gl_VertexID >> 2) + base_vertex;\n");
}
else
{
out.Write("\tuint vertex_id = uint(gl_VertexID) >> 2u;\n");
}
out.Write("\treturn input_buffer[vertex_id + index_offset];\n");
out.Write("}}\n\n");
}
static void WriteTransformMatrices(APIType api_type, const ShaderHostConfig& host_config,
const vertex_shader_uid_data* uid_data, ShaderCode& out)
{
@ -82,6 +104,11 @@ static void WriteTransformMatrices(APIType api_type, const ShaderHostConfig& hos
out.Write("\tmat3x4 result;\n");
if ((uid_data->components & VB_HAS_POSMTXIDX) != 0)
{
if (uid_data->vs_expand != VSExpand::None)
{
out.Write("\tInputData i = dolphin_primitive_expand_data(0);\n");
out.Write("\tuvec4 posmtx = unpack_ubyte4(i.posmtx);\n");
}
// Vertex format has a per-vertex matrix
out.Write("\tint posidx = int(posmtx.r);\n"
"\tresult[0] = " I_TRANSFORMMATRICES "[posidx];\n"
@ -108,6 +135,11 @@ static void WriteTransformMatrices(APIType api_type, const ShaderHostConfig& hos
out.Write("\tmat3 result;\n");
if ((uid_data->components & VB_HAS_POSMTXIDX) != 0)
{
if (uid_data->vs_expand != VSExpand::None)
{
out.Write("\tInputData i = dolphin_primitive_expand_data(0);\n");
out.Write("\tuvec4 posmtx = unpack_ubyte4(i.posmtx);\n");
}
// Vertex format has a per-vertex matrix
out.Write("\tint posidx = int(posmtx.r);\n");
out.Write("\tint normidx = posidx & 31;\n"
@ -401,7 +433,8 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
if (uid_data->components & VB_HAS_POSMTXIDX)
{
out.Write(" uint posmtx;\n");
input_extract.Write("uint4 posmtx = unpack_ubyte4(i.posmtx);\n");
// Note: posmtx is handled in the matrix transform functions and
// doesn't need to be added to 'input_extract'
}
if (uid_data->position_has_3_elems)
{
@ -507,6 +540,7 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
}
// Note: this is done after to ensure above global variables are accessible
WritePrimitiveExpand(api_type, host_config, uid_data, out);
WriteTransformMatrices(api_type, host_config, uid_data, out);
WriteTexCoordTransforms(api_type, host_config, uid_data, out);
WriteVertexDefines(api_type, host_config, uid_data, out);
@ -532,15 +566,7 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
if (uid_data->vs_expand != VSExpand::None)
{
out.Write("bool is_bottom = (gl_VertexID & 2) != 0;\n"
"bool is_right = (gl_VertexID & 1) != 0;\n");
// D3D doesn't include the base vertex in SV_VertexID
// See comment in UberShaderVertex for details
if (api_type == APIType::D3D)
out.Write("uint vertex_id = (gl_VertexID >> 2) + base_vertex;\n");
else
out.Write("uint vertex_id = uint(gl_VertexID) >> 2u;\n");
out.Write("InputData i = input_buffer[vertex_id];\n"
out.Write("InputData i = dolphin_primitive_expand_data(0);\n"
"{}",
input_extract.GetBuffer());
}
@ -691,14 +717,15 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
if (uid_data->vs_expand == VSExpand::Line)
{
out.Write("bool is_bottom = (gl_VertexID & 2) != 0;\n");
out.Write("// Line expansion\n"
"uint other_id = vertex_id;\n"
"int id_offset = 0;\n"
"if (is_bottom) {{\n"
" other_id -= 1u;\n"
" id_offset -= 1;\n"
"}} else {{\n"
" other_id += 1u;\n"
" id_offset += 1;\n"
"}}\n"
"InputData other = input_buffer[other_id];\n");
"InputData other = dolphin_primitive_expand_data(id_offset);\n");
if (uid_data->position_has_3_elems)
out.Write("float4 other_pos = float4(other.pos0, other.pos1, other.pos2, 1.0f);\n");
else
@ -716,10 +743,16 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
{
out.Write("other_pos = vec4(other_pos * dolphin_position_matrix(), 1.0);\n");
}
// Variable needed by GenerateVSLineExpansion
out.Write("bool is_right = (gl_VertexID & 1) != 0;\n");
GenerateVSLineExpansion(out, "", uid_data->numTexGens);
}
else if (uid_data->vs_expand == VSExpand::Point)
{
// Variables needed by GenerateVSPointExpansion
out.Write("bool is_bottom = (gl_VertexID & 2) != 0;\n");
out.Write("bool is_right = (gl_VertexID & 1) != 0;\n");
out.Write("// Point expansion\n");
GenerateVSPointExpansion(out, "", uid_data->numTexGens);
}

View file

@ -223,8 +223,6 @@ enum
XFMEM_SETMATRIXINDA = 0x1018,
XFMEM_SETMATRIXINDB = 0x1019,
XFMEM_SETVIEWPORT = 0x101a,
XFMEM_SETZSCALE = 0x101c,
XFMEM_SETZOFFSET = 0x101f,
XFMEM_SETPROJECTION = 0x1020,
// XFMEM_SETPROJECTIONB = 0x1021,
// XFMEM_SETPROJECTIONC = 0x1022,

View file

@ -46,7 +46,7 @@
% Document front page material
\title{\textbf{\Huge GameCube DSP User's Manual}}
\author{Reverse-engineered and documented by Duddie \\ \href{mailto:duddie@walla.com}{duddie@walla.com}}
\date{\today\\v0.1.7}
\date{\today\\v0.1.8}
% Title formatting commands
\newcommand{\OpcodeTitle}[1]{\subsection{#1}\label{instruction:#1}}
@ -265,6 +265,7 @@ The purpose of this documentation is purely academic and it aims at understandin
0.1.5 & 2022.09.29 & vpelletier & Fixed \texttt{BLOOP} and \texttt{BLOOPI} suboperation order \\ \hline
0.1.6 & 2022.06.20 & xperia64 & Accelerator documentation updates, fix register typo in ANDC and ORC descriptions \\ \hline
0.1.7 & 2025.04.21 & Tilka & Fixed typos and complained about GFDL \\ \hline
0.1.8 & 2025.07.27 & Tilka & Fixed some bit pattern inconsistencies in the 'LDAX* opcodes \\ \hline
\end{tabular}
\end{table}
@ -4615,7 +4616,7 @@ When the main and extension opcodes write to the same register, the register is
\begin{DSPOpcode}{'LDAXM}
\begin{DSPOpcodeBytefield}{16}
\monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{0011}
\monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{1011}
\end{DSPOpcodeBytefield}
\begin{DSPOpcodeFormat}
@ -4642,7 +4643,7 @@ When the main and extension opcodes write to the same register, the register is
\begin{DSPOpcode}{'LDAXNM}
\begin{DSPOpcodeBytefield}{16}
\monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{0011}
\monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{1111}
\end{DSPOpcodeBytefield}
\begin{DSPOpcodeFormat}
@ -4670,7 +4671,7 @@ When the main and extension opcodes write to the same register, the register is
\begin{DSPOpcode}{'LDAXN}
\begin{DSPOpcodeBytefield}{16}
\monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{0011}
\monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{0111}
\end{DSPOpcodeBytefield}
\begin{DSPOpcodeFormat}
@ -5214,7 +5215,7 @@ Instruction & Opcode & Page \\ \hline
\OpcodeRow{xxxx xxxx 11dr 10ss}{'LDM}
\OpcodeRow{xxxx xxxx 11sr 1011}{'LDAXM}
\OpcodeRow{xxxx xxxx 11dr 11ss}{'LDNM}
\OpcodeRow{xxxx xxxx 11dr 1111}{'LDAXNM}
\OpcodeRow{xxxx xxxx 11sr 1111}{'LDAXNM}
\end{longtable}
\end{center}

View file

@ -76,6 +76,7 @@
// Font data is encoded in 2 bit greyscale and in 8x8 blocks.
#include <cerrno>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <iostream>