mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-06-08 16:27:49 +00:00
Compare commits
7 Commits
34905645fc
...
e3dd7bf12c
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e3dd7bf12c | ||
![]() |
24b0bf01d5 | ||
![]() |
19fbbf0dba | ||
![]() |
2fd4a3500f | ||
![]() |
4b65cc9a4c | ||
![]() |
f99d3dbd5c | ||
![]() |
34e8fb068f |
@ -0,0 +1,28 @@
|
||||
package org.dolphinemu.dolphinemu.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioManager
|
||||
import androidx.annotation.Keep
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication
|
||||
|
||||
object AudioUtils {
|
||||
@JvmStatic @Keep
|
||||
fun getSampleRate(): Int =
|
||||
getAudioServiceProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE, 48000)
|
||||
|
||||
@JvmStatic @Keep
|
||||
fun getFramesPerBuffer(): Int =
|
||||
getAudioServiceProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER, 256)
|
||||
|
||||
private fun getAudioServiceProperty(property: String, fallback: Int): Int {
|
||||
return try {
|
||||
val context = DolphinApplication.getAppContext()
|
||||
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
Integer.parseUnsignedInt(am.getProperty(property))
|
||||
} catch (e: NullPointerException) {
|
||||
fallback
|
||||
} catch (e: NumberFormatException) {
|
||||
fallback
|
||||
}
|
||||
}
|
||||
}
|
@ -122,6 +122,10 @@ static jmethodID s_permission_handler_request_record_audio_permission;
|
||||
|
||||
static jmethodID s_runnable_run;
|
||||
|
||||
static jclass s_audio_utils_class;
|
||||
static jmethodID s_audio_utils_get_sample_rate;
|
||||
static jmethodID s_audio_utils_get_frames_per_buffer;
|
||||
|
||||
namespace IDCache
|
||||
{
|
||||
JNIEnv* GetEnvForThread()
|
||||
@ -562,6 +566,21 @@ jmethodID GetRunnableRun()
|
||||
return s_runnable_run;
|
||||
}
|
||||
|
||||
jclass GetAudioUtilsClass()
|
||||
{
|
||||
return s_audio_utils_class;
|
||||
}
|
||||
|
||||
jmethodID GetAudioUtilsGetSampleRate()
|
||||
{
|
||||
return s_audio_utils_get_sample_rate;
|
||||
}
|
||||
|
||||
jmethodID GetAudioUtilsGetFramesPerBuffer()
|
||||
{
|
||||
return s_audio_utils_get_frames_per_buffer;
|
||||
}
|
||||
|
||||
} // namespace IDCache
|
||||
|
||||
extern "C" {
|
||||
@ -798,6 +817,13 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||
s_runnable_run = env->GetMethodID(runnable_class, "run", "()V");
|
||||
env->DeleteLocalRef(runnable_class);
|
||||
|
||||
const jclass audio_utils_class = env->FindClass("org/dolphinemu/dolphinemu/utils/AudioUtils");
|
||||
s_audio_utils_class = reinterpret_cast<jclass>(env->NewGlobalRef(audio_utils_class));
|
||||
s_audio_utils_get_sample_rate = env->GetStaticMethodID(audio_utils_class, "getSampleRate", "()I");
|
||||
s_audio_utils_get_frames_per_buffer =
|
||||
env->GetStaticMethodID(audio_utils_class, "getFramesPerBuffer", "()I");
|
||||
env->DeleteLocalRef(audio_utils_class);
|
||||
|
||||
return JNI_VERSION;
|
||||
}
|
||||
|
||||
@ -834,5 +860,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
|
||||
env->DeleteGlobalRef(s_core_device_control_class);
|
||||
env->DeleteGlobalRef(s_input_detector_class);
|
||||
env->DeleteGlobalRef(s_permission_handler_class);
|
||||
env->DeleteGlobalRef(s_audio_utils_class);
|
||||
}
|
||||
}
|
||||
|
@ -121,4 +121,8 @@ jmethodID GetPermissionHandlerRequestRecordAudioPermission();
|
||||
|
||||
jmethodID GetRunnableRun();
|
||||
|
||||
jclass GetAudioUtilsClass();
|
||||
jmethodID GetAudioUtilsGetSampleRate();
|
||||
jmethodID GetAudioUtilsGetFramesPerBuffer();
|
||||
|
||||
} // namespace IDCache
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "AudioCommon/WASAPIStream.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/System.h"
|
||||
@ -219,8 +220,11 @@ void StartAudioDump(Core::System& system)
|
||||
|
||||
std::string path_prefix = File::GetUserPath(D_DUMPAUDIO_IDX) + SConfig::GetInstance().GetGameID();
|
||||
|
||||
std::string base_name =
|
||||
fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}", path_prefix, fmt::localtime(start_time));
|
||||
const auto local_time = Common::LocalTime(start_time);
|
||||
if (!local_time)
|
||||
return;
|
||||
|
||||
std::string base_name = fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}", path_prefix, *local_time);
|
||||
|
||||
const std::string audio_file_name_dtk = fmt::format("{}_dtkdump.wav", base_name);
|
||||
const std::string audio_file_name_dsp = fmt::format("{}_dspdump.wav", base_name);
|
||||
|
@ -8,40 +8,27 @@
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "jni/AndroidCommon/IDCache.h"
|
||||
|
||||
// engine interfaces
|
||||
static SLObjectItf engineObject;
|
||||
static SLEngineItf engineEngine;
|
||||
static SLObjectItf outputMixObject;
|
||||
|
||||
// buffer queue player interfaces
|
||||
static SLObjectItf bqPlayerObject = nullptr;
|
||||
static SLPlayItf bqPlayerPlay;
|
||||
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
|
||||
static SLVolumeItf bqPlayerVolume;
|
||||
static Mixer* g_mixer;
|
||||
#define BUFFER_SIZE 512
|
||||
#define BUFFER_SIZE_IN_SAMPLES (BUFFER_SIZE / 2)
|
||||
|
||||
// Double buffering.
|
||||
static short buffer[2][BUFFER_SIZE];
|
||||
static int curBuffer = 0;
|
||||
|
||||
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
||||
void OpenSLESStream::BQPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
||||
{
|
||||
ASSERT(bq == bqPlayerBufferQueue);
|
||||
ASSERT(nullptr == context);
|
||||
reinterpret_cast<OpenSLESStream*>(context)->PushSamples(bq);
|
||||
}
|
||||
|
||||
void OpenSLESStream::PushSamples(SLAndroidSimpleBufferQueueItf bq)
|
||||
{
|
||||
ASSERT(bq == m_bq_player_buffer_queue);
|
||||
|
||||
// Render to the fresh buffer
|
||||
g_mixer->Mix(reinterpret_cast<short*>(buffer[curBuffer]), BUFFER_SIZE_IN_SAMPLES);
|
||||
SLresult result =
|
||||
(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[curBuffer], sizeof(buffer[0]));
|
||||
curBuffer ^= 1; // Switch buffer
|
||||
m_mixer->Mix(m_buffer[m_current_buffer].data(), m_frames_per_buffer);
|
||||
SLresult result = (*bq)->Enqueue(bq, m_buffer[m_current_buffer].data(), m_bytes_per_buffer);
|
||||
m_current_buffer ^= 1; // Switch buffer
|
||||
|
||||
// Comment from sample code:
|
||||
// the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
|
||||
@ -51,61 +38,78 @@ static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
||||
|
||||
bool OpenSLESStream::Init()
|
||||
{
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
jclass audio_utils = IDCache::GetAudioUtilsClass();
|
||||
const SLuint32 sample_rate =
|
||||
env->CallStaticIntMethod(audio_utils, IDCache::GetAudioUtilsGetSampleRate());
|
||||
m_frames_per_buffer =
|
||||
env->CallStaticIntMethod(audio_utils, IDCache::GetAudioUtilsGetFramesPerBuffer());
|
||||
|
||||
INFO_LOG_FMT(AUDIO, "OpenSLES configuration: {} Hz, {} frames per buffer", sample_rate,
|
||||
m_frames_per_buffer);
|
||||
|
||||
constexpr SLuint32 channels = 2;
|
||||
const SLuint32 samples_per_buffer = m_frames_per_buffer * channels;
|
||||
m_bytes_per_buffer = m_frames_per_buffer * channels * sizeof(m_buffer[0][0]);
|
||||
|
||||
for (std::vector<short>& buffer : m_buffer)
|
||||
buffer.resize(samples_per_buffer);
|
||||
|
||||
SLresult result;
|
||||
// create engine
|
||||
result = slCreateEngine(&engineObject, 0, nullptr, 0, nullptr, nullptr);
|
||||
result = slCreateEngine(&m_engine_object, 0, nullptr, 0, nullptr, nullptr);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
|
||||
result = (*m_engine_object)->Realize(m_engine_object, SL_BOOLEAN_FALSE);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
|
||||
result = (*m_engine_object)->GetInterface(m_engine_object, SL_IID_ENGINE, &m_engine_engine);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
|
||||
result = (*m_engine_engine)->CreateOutputMix(m_engine_engine, &m_output_mix_object, 0, 0, 0);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
|
||||
result = (*m_output_mix_object)->Realize(m_output_mix_object, SL_BOOLEAN_FALSE);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
||||
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,
|
||||
2,
|
||||
m_mixer->GetSampleRate() * 1000,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
||||
SL_BYTEORDER_LITTLEENDIAN};
|
||||
SLDataFormat_PCM format_pcm = {
|
||||
SL_DATAFORMAT_PCM, channels,
|
||||
sample_rate * 1000, SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
||||
SL_BYTEORDER_LITTLEENDIAN};
|
||||
|
||||
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
|
||||
|
||||
// configure audio sink
|
||||
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
|
||||
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, m_output_mix_object};
|
||||
SLDataSink audioSnk = {&loc_outmix, nullptr};
|
||||
|
||||
// create audio player
|
||||
const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
|
||||
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
||||
result =
|
||||
(*engineEngine)
|
||||
->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req);
|
||||
result = (*m_engine_engine)
|
||||
->CreateAudioPlayer(m_engine_engine, &m_bq_player_object, &audioSrc, &audioSnk, 2,
|
||||
ids, req);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
|
||||
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
|
||||
result = (*m_bq_player_object)->Realize(m_bq_player_object, SL_BOOLEAN_FALSE);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
|
||||
result = (*m_bq_player_object)->GetInterface(m_bq_player_object, SL_IID_PLAY, &m_bq_player_play);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
result = (*m_bq_player_object)
|
||||
->GetInterface(m_bq_player_object, SL_IID_BUFFERQUEUE, &m_bq_player_buffer_queue);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
result =
|
||||
(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
|
||||
(*m_bq_player_object)->GetInterface(m_bq_player_object, SL_IID_VOLUME, &m_bq_player_volume);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
|
||||
result = (*m_bq_player_buffer_queue)
|
||||
->RegisterCallback(m_bq_player_buffer_queue, BQPlayerCallback, this);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, nullptr);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
result = (*m_bq_player_play)->SetPlayState(m_bq_player_play, SL_PLAYSTATE_PLAYING);
|
||||
ASSERT(SL_RESULT_SUCCESS == result);
|
||||
|
||||
// Render and enqueue a first buffer.
|
||||
curBuffer ^= 1;
|
||||
g_mixer = m_mixer.get();
|
||||
m_current_buffer ^= 1;
|
||||
|
||||
result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[0], sizeof(buffer[0]));
|
||||
result = (*m_bq_player_buffer_queue)
|
||||
->Enqueue(m_bq_player_buffer_queue, m_buffer[0].data(), m_bytes_per_buffer);
|
||||
if (SL_RESULT_SUCCESS != result)
|
||||
return false;
|
||||
|
||||
@ -114,39 +118,39 @@ bool OpenSLESStream::Init()
|
||||
|
||||
OpenSLESStream::~OpenSLESStream()
|
||||
{
|
||||
if (bqPlayerObject != nullptr)
|
||||
if (m_bq_player_object != nullptr)
|
||||
{
|
||||
(*bqPlayerObject)->Destroy(bqPlayerObject);
|
||||
bqPlayerObject = nullptr;
|
||||
bqPlayerPlay = nullptr;
|
||||
bqPlayerBufferQueue = nullptr;
|
||||
bqPlayerVolume = nullptr;
|
||||
(*m_bq_player_object)->Destroy(m_bq_player_object);
|
||||
m_bq_player_object = nullptr;
|
||||
m_bq_player_play = nullptr;
|
||||
m_bq_player_buffer_queue = nullptr;
|
||||
m_bq_player_volume = nullptr;
|
||||
}
|
||||
|
||||
if (outputMixObject != nullptr)
|
||||
if (m_output_mix_object != nullptr)
|
||||
{
|
||||
(*outputMixObject)->Destroy(outputMixObject);
|
||||
outputMixObject = nullptr;
|
||||
(*m_output_mix_object)->Destroy(m_output_mix_object);
|
||||
m_output_mix_object = nullptr;
|
||||
}
|
||||
|
||||
if (engineObject != nullptr)
|
||||
if (m_engine_object != nullptr)
|
||||
{
|
||||
(*engineObject)->Destroy(engineObject);
|
||||
engineObject = nullptr;
|
||||
engineEngine = nullptr;
|
||||
(*m_engine_object)->Destroy(m_engine_object);
|
||||
m_engine_object = nullptr;
|
||||
m_engine_engine = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenSLESStream::SetRunning(bool running)
|
||||
{
|
||||
SLuint32 new_state = running ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_PAUSED;
|
||||
return (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, new_state) == SL_RESULT_SUCCESS;
|
||||
return (*m_bq_player_play)->SetPlayState(m_bq_player_play, new_state) == SL_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void OpenSLESStream::SetVolume(int volume)
|
||||
{
|
||||
const SLmillibel attenuation =
|
||||
volume <= 0 ? SL_MILLIBEL_MIN : static_cast<SLmillibel>(2000 * std::log10(volume / 100.0f));
|
||||
(*bqPlayerVolume)->SetVolumeLevel(bqPlayerVolume, attenuation);
|
||||
(*m_bq_player_volume)->SetVolumeLevel(m_bq_player_volume, attenuation);
|
||||
}
|
||||
#endif // HAVE_OPENSL_ES
|
||||
|
@ -3,10 +3,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#ifdef HAVE_OPENSL_ES
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#endif // HAVE_OPENSL_ES
|
||||
|
||||
#include "AudioCommon/SoundStream.h"
|
||||
#include "Common/Event.h"
|
||||
|
||||
class OpenSLESStream final : public SoundStream
|
||||
{
|
||||
@ -19,7 +24,25 @@ public:
|
||||
static bool IsValid() { return true; }
|
||||
|
||||
private:
|
||||
std::thread thread;
|
||||
Common::Event soundSyncEvent;
|
||||
static void BQPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context);
|
||||
void PushSamples(SLAndroidSimpleBufferQueueItf bq);
|
||||
|
||||
// engine interfaces
|
||||
SLObjectItf m_engine_object;
|
||||
SLEngineItf m_engine_engine;
|
||||
SLObjectItf m_output_mix_object;
|
||||
|
||||
// buffer queue player interfaces
|
||||
SLObjectItf m_bq_player_object = nullptr;
|
||||
SLPlayItf m_bq_player_play;
|
||||
SLAndroidSimpleBufferQueueItf m_bq_player_buffer_queue;
|
||||
SLVolumeItf m_bq_player_volume;
|
||||
|
||||
SLuint32 m_frames_per_buffer;
|
||||
SLuint32 m_bytes_per_buffer;
|
||||
|
||||
// Double buffering.
|
||||
std::array<std::vector<short>, 2> m_buffer;
|
||||
int m_current_buffer = 0;
|
||||
#endif // HAVE_OPENSL_ES
|
||||
};
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/ScopeGuard.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
|
||||
@ -95,12 +96,7 @@ int SDCardDiskIOCtl(File::IOFile* image, u8 pdrv, u8 cmd, void* buff)
|
||||
u32 GetSystemTimeFAT()
|
||||
{
|
||||
const std::time_t time = std::time(nullptr);
|
||||
std::tm tm;
|
||||
#ifdef _WIN32
|
||||
localtime_s(&tm, &time);
|
||||
#else
|
||||
localtime_r(&time, &tm);
|
||||
#endif
|
||||
std::tm tm = *Common::LocalTime(time);
|
||||
|
||||
DWORD fattime = 0;
|
||||
fattime |= (tm.tm_year - 80) << 25;
|
||||
|
@ -122,7 +122,6 @@ std::string SettingsWriter::GenerateSerialNumber()
|
||||
|
||||
// Must be 9 characters at most; otherwise the serial number will be rejected by SDK libraries,
|
||||
// as there is a check to ensure the string length is strictly lower than 10.
|
||||
// 3 for %j, 2 for %H, 2 for %M, 2 for %S.
|
||||
return fmt::format("{:%j%H%M%S}", fmt::localtime(t));
|
||||
return fmt::format("{:09}", t % 1000000000);
|
||||
}
|
||||
} // namespace Common
|
||||
|
@ -2,23 +2,25 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <optional>
|
||||
|
||||
namespace Common
|
||||
{
|
||||
std::optional<std::tm> Localtime(std::time_t time)
|
||||
std::optional<std::tm> LocalTime(std::time_t time)
|
||||
{
|
||||
std::tm local_time;
|
||||
#ifdef _MSC_VER
|
||||
if (localtime_s(&local_time, &time) != 0)
|
||||
return std::nullopt;
|
||||
#else
|
||||
std::tm* result = localtime_r(&time, &local_time);
|
||||
if (result != &local_time)
|
||||
return std::nullopt;
|
||||
if (localtime_r(&time, &local_time) == NULL)
|
||||
#endif
|
||||
{
|
||||
ERROR_LOG_FMT(COMMON, "Failed to convert time to local time: {}", std::strerror(errno));
|
||||
return std::nullopt;
|
||||
}
|
||||
return local_time;
|
||||
}
|
||||
} // Namespace Common
|
||||
|
@ -9,5 +9,5 @@
|
||||
namespace Common
|
||||
{
|
||||
// Threadsafe and error-checking variant of std::localtime()
|
||||
std::optional<std::tm> Localtime(std::time_t time);
|
||||
std::optional<std::tm> LocalTime(std::time_t time);
|
||||
} // Namespace Common
|
||||
|
@ -554,7 +554,7 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
|
||||
if (!EmulatedBS2(system, guard, system.IsWii(), *volume, riivolution_patches))
|
||||
return false;
|
||||
|
||||
SConfig::OnTitleDirectlyBooted(guard);
|
||||
// SConfig::OnTitleDirectlyBooted(guard) is called by apploader runner code
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
@ -172,12 +173,17 @@ public:
|
||||
static bool FindMapFile(std::string* existing_map_file, std::string* writable_map_file);
|
||||
static bool LoadMapFromFilename(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db);
|
||||
|
||||
static void HLEAppLoaderAfterEntry(const Core::CPUThreadGuard& guard);
|
||||
static void HLEAppLoaderAfterInit(const Core::CPUThreadGuard& guard);
|
||||
static void HLEAppLoaderAfterMain(const Core::CPUThreadGuard& guard);
|
||||
static void HLEAppLoaderAfterClose(const Core::CPUThreadGuard& guard);
|
||||
static void HLEAppLoaderReport(std::string_view message);
|
||||
|
||||
private:
|
||||
static bool DVDRead(Core::System& system, const DiscIO::VolumeDisc& disc, u64 dvd_offset,
|
||||
u32 output_address, u32 length, const DiscIO::Partition& partition);
|
||||
static bool DVDReadDiscID(Core::System& system, const DiscIO::VolumeDisc& disc,
|
||||
u32 output_address);
|
||||
static void RunFunction(Core::System& system, u32 address);
|
||||
|
||||
static bool Boot_WiiWAD(Core::System& system, const DiscIO::VolumeWAD& wad);
|
||||
static bool BootNANDTitle(Core::System& system, u64 title_id);
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "Core/HW/DVD/DVDInterface.h"
|
||||
#include "Core/HW/EXI/EXI_DeviceIPL.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/IOS/DI/DI.h"
|
||||
#include "Core/IOS/ES/ES.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
@ -32,6 +33,7 @@
|
||||
#include "Core/IOS/IOSC.h"
|
||||
#include "Core/IOS/Uids.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
@ -56,18 +58,6 @@ void PresetTimeBaseTicks(Core::System& system, const Core::CPUThreadGuard& guard
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void CBoot::RunFunction(Core::System& system, u32 address)
|
||||
{
|
||||
auto& power_pc = system.GetPowerPC();
|
||||
auto& ppc_state = power_pc.GetPPCState();
|
||||
|
||||
ppc_state.pc = address;
|
||||
LR(ppc_state) = 0x00;
|
||||
|
||||
while (ppc_state.pc != 0x00)
|
||||
power_pc.SingleStep();
|
||||
}
|
||||
|
||||
void CBoot::SetupMSR(PowerPC::PowerPCState& ppc_state)
|
||||
{
|
||||
// 0x0002032
|
||||
@ -136,6 +126,135 @@ void CBoot::SetupBAT(Core::System& system, bool is_wii)
|
||||
mmu.IBATUpdated();
|
||||
}
|
||||
|
||||
/*
|
||||
#include <ogc/machine/asm.h>
|
||||
|
||||
.globl AppLoaderRunnerBase
|
||||
AppLoaderRunnerBase:
|
||||
b RunAppLoader
|
||||
|
||||
// Variables that iAppLoaderMain stores into
|
||||
AppLoaderMainRamAddress:
|
||||
.4byte 0x00dead04
|
||||
AppLoaderMainLength:
|
||||
.4byte 0x00dead08
|
||||
AppLoaderMainDiscOffset:
|
||||
.4byte 0x00dead0c
|
||||
|
||||
// Function pointers returned by iAppLoaderEntry
|
||||
AppLoaderInitPointer: // pointer to: void iAppLoaderInit(void (*OSReport)(char *fmt, ...))
|
||||
.4byte 0x00dead10
|
||||
AppLoaderMainPointer: // pointer to: bool iAppLoaderMain(u32* ram_addr, u32* len, u32* disc_offset)
|
||||
.4byte 0x00dead14
|
||||
AppLoaderClosePointer: // pointer to: void* iAppLoaderClose(void);
|
||||
.4byte 0x00dead18
|
||||
|
||||
HLEAppLoaderAfterEntry: // void HLEAppLoaderAfterEntry(void), logging
|
||||
blr
|
||||
HLEAppLoaderAfterInit: // void HLEAppLoaderAfterInit(void), logging
|
||||
blr
|
||||
HLEAppLoaderAfterMain: // void HLEAppLoaderAfterMain(void), reads disc at AppLoaderMain* variables
|
||||
blr
|
||||
HLEAppLoaderAfterClose: // void HLEAppLoaderAfterClose(void), cleans up HLE state
|
||||
blr
|
||||
HLEAppLoaderReport: // void HLEAppLoaderReport(char *fmt, ...), passed to iAppLoaderInit
|
||||
blr
|
||||
|
||||
RunAppLoader:
|
||||
// Entry point; call iAppLoaderEntry
|
||||
// r31 is AppLoaderRunnerBase
|
||||
// r30 is iAppLoaderEntry (later used as a scratch register for other mtctr + bctrl sequences)
|
||||
// Note that the above registers are specific to Dolphin's apploader HLE.
|
||||
addi r3, r31, (AppLoaderInitPointer - AppLoaderRunnerBase)
|
||||
addi r4, r31, (AppLoaderMainPointer - AppLoaderRunnerBase)
|
||||
addi r5, r31, (AppLoaderClosePointer - AppLoaderRunnerBase)
|
||||
mtctr r30
|
||||
bctrl // Call iAppLoaderEntry, which looks like this:
|
||||
// void iAppLoaderEntry(void** init, void** main, void** close)
|
||||
// {
|
||||
// *init = &iAppLoaderInit;
|
||||
// *main = &iAppLoaderMain;
|
||||
// *close = &iAppLoaderClose;
|
||||
// }
|
||||
bl HLEAppLoaderAfterEntry // Logging only
|
||||
|
||||
// Call iAppLoaderInit, which sets up logging and any internal state.
|
||||
addi r3, r31, (HLEAppLoaderReport - AppLoaderRunnerBase)
|
||||
lwz r30, (AppLoaderInitPointer - AppLoaderRunnerBase)(r31)
|
||||
mtctr r30
|
||||
bctrl // Call iAppLoaderInit
|
||||
bl HLEAppLoaderAfterInit // Logging only
|
||||
|
||||
// iAppLoaderMain - Here we load the apploader, the DOL (the exe) and the FST (filesystem).
|
||||
// To give you an idea about where the stuff is located on the disc take a look at yagcd
|
||||
// ch 13.
|
||||
// iAppLoaderMain returns 1 if the pointers in R3/R4/R5 were filled with values for DVD copy
|
||||
// Typical behaviour is doing it once for each section defined in the DOL header. Some unlicensed
|
||||
// titles don't have a properly constructed DOL and maintain a table of these values in apploader.
|
||||
// iAppLoaderMain returns 0 when there are no more sections to copy.
|
||||
LoopMain:
|
||||
addi r3, r31, (AppLoaderMainRamAddress - AppLoaderRunnerBase)
|
||||
addi r4, r31, (AppLoaderMainLength - AppLoaderRunnerBase)
|
||||
addi r5, r31, (AppLoaderMainDiscOffset - AppLoaderRunnerBase)
|
||||
lwz r30, (AppLoaderMainPointer - AppLoaderRunnerBase)(r31)
|
||||
mtctr r30
|
||||
bctrl // Call iAppLoaderMain
|
||||
cmpwi r3,0
|
||||
beq LoopMainDone
|
||||
bl HLEAppLoaderAfterMain // Reads the disc (and logs)
|
||||
b LoopMain
|
||||
|
||||
// Call iAppLoaderClose, which returns (in r3) the game's entry point
|
||||
LoopMainDone:
|
||||
lwz r30, (AppLoaderClosePointer - AppLoaderRunnerBase)(r31)
|
||||
mtctr r30
|
||||
bctrl // Call iAppLoaderClose
|
||||
mr r30, r3
|
||||
bl HLEAppLoaderAfterClose // Unpatches the HLE functions
|
||||
mtlr r30
|
||||
blr // "Return" to the game's entry point; this allows using "step out" to skip the apploader
|
||||
*/
|
||||
static constexpr u32 APPLOADER_RUNNER_BASE = 0x81300000;
|
||||
static constexpr u32 APPLOADER_RUNNER_MAIN_RAM_ADDR = APPLOADER_RUNNER_BASE + 0x04;
|
||||
static constexpr u32 APPLOADER_RUNNER_MAIN_LENGTH = APPLOADER_RUNNER_BASE + 0x08;
|
||||
static constexpr u32 APPLOADER_RUNNER_MAIN_DISC_OFFSET = APPLOADER_RUNNER_BASE + 0x0c;
|
||||
static constexpr u32 APPLOADER_RUNNER_INIT_POINTER = APPLOADER_RUNNER_BASE + 0x10;
|
||||
static constexpr u32 APPLOADER_RUNNER_MAIN_POINTER = APPLOADER_RUNNER_BASE + 0x14;
|
||||
static constexpr u32 APPLOADER_RUNNER_CLOSE_POINTER = APPLOADER_RUNNER_BASE + 0x18;
|
||||
static constexpr u32 APPLOADER_RUNNER_HLE_AFTER_ENTRY = APPLOADER_RUNNER_BASE + 0x1c;
|
||||
static constexpr u32 APPLOADER_RUNNER_HLE_AFTER_INIT = APPLOADER_RUNNER_BASE + 0x20;
|
||||
static constexpr u32 APPLOADER_RUNNER_HLE_AFTER_MAIN = APPLOADER_RUNNER_BASE + 0x24;
|
||||
static constexpr u32 APPLOADER_RUNNER_HLE_AFTER_CLOSE = APPLOADER_RUNNER_BASE + 0x28;
|
||||
static constexpr u32 APPLOADER_RUNNER_HLE_REPORT = APPLOADER_RUNNER_BASE + 0x2c;
|
||||
static constexpr u32 APPLOADER_RUNNER_RUN_ADDR = APPLOADER_RUNNER_BASE + 0x30;
|
||||
|
||||
static constexpr std::array<u32, 40> APPLOADER_RUNNER_ASM{
|
||||
0x48000030, // Starting thunk
|
||||
// Variables
|
||||
0x00dead04, 0x00dead08, 0x00dead0c, 0x00dead10, 0x00dead14, 0x00dead18,
|
||||
// HLE'd functions
|
||||
0x4e800020, 0x4e800020, 0x4e800020, 0x4e800020, 0x4e800020,
|
||||
// Actual code
|
||||
0x387f0010, 0x389f0014, 0x38bf0018, 0x7fc903a6, 0x4e800421, 0x4bffffd9, 0x387f002c, 0x83df0010,
|
||||
0x7fc903a6, 0x4e800421, 0x4bffffc9, 0x387f0004, 0x389f0008, 0x38bf000c, 0x83df0014, 0x7fc903a6,
|
||||
0x4e800421, 0x2c030000, 0x4182000c, 0x4bffffa9, 0x4bffffdc, 0x83df0018, 0x7fc903a6, 0x4e800421,
|
||||
0x7c7e1b78, 0x4bffff95, 0x7fc803a6, 0x4e800020};
|
||||
|
||||
struct ApploaderRunnerState
|
||||
{
|
||||
constexpr ApploaderRunnerState(
|
||||
bool is_wii_, const DiscIO::VolumeDisc& volume_,
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches_)
|
||||
: is_wii(is_wii_), volume(volume_), riivolution_patches(riivolution_patches_)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_wii;
|
||||
const DiscIO::VolumeDisc& volume;
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches;
|
||||
};
|
||||
static std::unique_ptr<ApploaderRunnerState> s_apploader_runner_state;
|
||||
|
||||
bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard, bool is_wii,
|
||||
const DiscIO::VolumeDisc& volume,
|
||||
const std::vector<DiscIO::Riivolution::Patch>& riivolution_patches)
|
||||
@ -153,11 +272,13 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard
|
||||
INFO_LOG_FMT(BOOT, "Invalid apploader. Your disc image is probably corrupted.");
|
||||
return false;
|
||||
}
|
||||
INFO_LOG_FMT(BOOT,
|
||||
"Loading apploader ({}) from disc offset {:08x} to {:08x}, "
|
||||
"size {:#x} ({:#x} + {:#x}), entry point {:08x}",
|
||||
volume.GetApploaderDate(partition), offset + 0x20, 0x81200000, *size + *trailer,
|
||||
*size, *trailer, *entry);
|
||||
DVDRead(system, volume, offset + 0x20, 0x01200000, *size + *trailer, partition);
|
||||
|
||||
// TODO - Make Apploader(or just RunFunction()) debuggable!!!
|
||||
|
||||
auto& ppc_state = system.GetPPCState();
|
||||
auto& mmu = system.GetMMU();
|
||||
auto& branch_watch = system.GetPowerPC().GetBranchWatch();
|
||||
|
||||
@ -165,66 +286,66 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard
|
||||
if (system.IsBranchWatchIgnoreApploader())
|
||||
branch_watch.SetRecordingActive(guard, false);
|
||||
|
||||
// Call iAppLoaderEntry.
|
||||
DEBUG_LOG_FMT(BOOT, "Call iAppLoaderEntry");
|
||||
const u32 iAppLoaderFuncAddr = is_wii ? 0x80004000 : 0x80003100;
|
||||
ppc_state.gpr[3] = iAppLoaderFuncAddr + 0;
|
||||
ppc_state.gpr[4] = iAppLoaderFuncAddr + 4;
|
||||
ppc_state.gpr[5] = iAppLoaderFuncAddr + 8;
|
||||
RunFunction(system, *entry);
|
||||
const u32 iAppLoaderInit = mmu.Read_U32(iAppLoaderFuncAddr + 0);
|
||||
const u32 iAppLoaderMain = mmu.Read_U32(iAppLoaderFuncAddr + 4);
|
||||
const u32 iAppLoaderClose = mmu.Read_U32(iAppLoaderFuncAddr + 8);
|
||||
|
||||
// iAppLoaderInit
|
||||
DEBUG_LOG_FMT(BOOT, "Call iAppLoaderInit");
|
||||
PowerPC::MMU::HostWrite_U32(guard, 0x4E800020, 0x81300000); // Write BLR
|
||||
HLE::Patch(system, 0x81300000, "AppLoaderReport"); // HLE OSReport for Apploader
|
||||
ppc_state.gpr[3] = 0x81300000;
|
||||
RunFunction(system, iAppLoaderInit);
|
||||
|
||||
// iAppLoaderMain - Here we load the apploader, the DOL (the exe) and the FST (filesystem).
|
||||
// To give you an idea about where the stuff is located on the disc take a look at yagcd
|
||||
// ch 13.
|
||||
DEBUG_LOG_FMT(BOOT, "Call iAppLoaderMain");
|
||||
|
||||
ppc_state.gpr[3] = 0x81300004;
|
||||
ppc_state.gpr[4] = 0x81300008;
|
||||
ppc_state.gpr[5] = 0x8130000c;
|
||||
|
||||
RunFunction(system, iAppLoaderMain);
|
||||
|
||||
// iAppLoaderMain returns 1 if the pointers in R3/R4/R5 were filled with values for DVD copy
|
||||
// Typical behaviour is doing it once for each section defined in the DOL header. Some unlicensed
|
||||
// titles don't have a properly constructed DOL and maintain a table of these values in apploader.
|
||||
// iAppLoaderMain returns 0 when there are no more sections to copy.
|
||||
while (ppc_state.gpr[3] != 0x00)
|
||||
for (u32 i = 0; i < APPLOADER_RUNNER_ASM.size(); i++)
|
||||
{
|
||||
const u32 ram_address = mmu.Read_U32(0x81300004);
|
||||
const u32 length = mmu.Read_U32(0x81300008);
|
||||
const u32 dvd_offset = mmu.Read_U32(0x8130000c) << (is_wii ? 2 : 0);
|
||||
|
||||
INFO_LOG_FMT(BOOT, "DVDRead: offset: {:08x} memOffset: {:08x} length: {}", dvd_offset,
|
||||
ram_address, length);
|
||||
DVDRead(system, volume, dvd_offset, ram_address, length, partition);
|
||||
|
||||
DiscIO::Riivolution::ApplyApploaderMemoryPatches(guard, riivolution_patches, ram_address,
|
||||
length);
|
||||
|
||||
ppc_state.gpr[3] = 0x81300004;
|
||||
ppc_state.gpr[4] = 0x81300008;
|
||||
ppc_state.gpr[5] = 0x8130000c;
|
||||
|
||||
RunFunction(system, iAppLoaderMain);
|
||||
mmu.HostWrite_U32(guard, APPLOADER_RUNNER_ASM[i], APPLOADER_RUNNER_BASE + i * sizeof(u32));
|
||||
}
|
||||
|
||||
// iAppLoaderClose
|
||||
DEBUG_LOG_FMT(BOOT, "call iAppLoaderClose");
|
||||
RunFunction(system, iAppLoaderClose);
|
||||
HLE::UnPatch(system, "AppLoaderReport");
|
||||
HLE::Patch(system, APPLOADER_RUNNER_HLE_AFTER_ENTRY, "HLEAppLoaderAfterEntry");
|
||||
HLE::Patch(system, APPLOADER_RUNNER_HLE_AFTER_INIT, "HLEAppLoaderAfterInit");
|
||||
HLE::Patch(system, APPLOADER_RUNNER_HLE_AFTER_MAIN, "HLEAppLoaderAfterMain");
|
||||
HLE::Patch(system, APPLOADER_RUNNER_HLE_AFTER_CLOSE, "HLEAppLoaderAfterClose");
|
||||
HLE::Patch(system, APPLOADER_RUNNER_HLE_REPORT, "HLEAppLoaderReport");
|
||||
s_apploader_runner_state =
|
||||
std::make_unique<ApploaderRunnerState>(is_wii, volume, riivolution_patches);
|
||||
|
||||
// return
|
||||
ppc_state.pc = ppc_state.gpr[3];
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, *entry, 0, "iAppLoaderEntry", "Apploader");
|
||||
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_MAIN_RAM_ADDR, 4,
|
||||
"AppLoaderMainRamAddress", "Apploader",
|
||||
Common::Symbol::Type::Data);
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_MAIN_LENGTH, 4,
|
||||
"AppLoaderMainLength", "Apploader",
|
||||
Common::Symbol::Type::Data);
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_MAIN_DISC_OFFSET, 4,
|
||||
"AppLoaderMainDiscOffset", "Apploader",
|
||||
Common::Symbol::Type::Data);
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_INIT_POINTER, 4,
|
||||
"AppLoaderInitPointer", "Apploader",
|
||||
Common::Symbol::Type::Data);
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_MAIN_POINTER, 4,
|
||||
"AppLoaderMainPointer", "Apploader",
|
||||
Common::Symbol::Type::Data);
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_CLOSE_POINTER, 4,
|
||||
"AppLoaderClosePointer", "Apploader",
|
||||
Common::Symbol::Type::Data);
|
||||
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_HLE_AFTER_ENTRY, 4,
|
||||
"HLEAppLoaderAfterEntry", "Dolphin BS2 HLE");
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_HLE_AFTER_INIT, 4,
|
||||
"HLEAppLoaderAfterInit", "Dolphin BS2 HLE");
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_HLE_AFTER_MAIN, 4,
|
||||
"HLEAppLoaderAfterMain", "Dolphin BS2 HLE");
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_HLE_AFTER_CLOSE, 4,
|
||||
"HLEAppLoaderAfterClose", "Dolphin BS2 HLE");
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_HLE_REPORT, 4,
|
||||
"HLEAppLoaderReport", "Dolphin BS2 HLE");
|
||||
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_BASE, 4, "DolphinApploaderRunner",
|
||||
"Dolphin BS2 HLE");
|
||||
const u32 remainder_size = u32(APPLOADER_RUNNER_ASM.size() * sizeof(u32)) -
|
||||
(APPLOADER_RUNNER_RUN_ADDR - APPLOADER_RUNNER_BASE);
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, APPLOADER_RUNNER_RUN_ADDR, remainder_size,
|
||||
"DolphinApploaderRunner", "Dolphin BS2 HLE");
|
||||
system.GetPPCSymbolDB().Index();
|
||||
Host_PPCSymbolsChanged();
|
||||
|
||||
auto& ppc_state = system.GetPPCState();
|
||||
ppc_state.gpr[31] = APPLOADER_RUNNER_BASE;
|
||||
ppc_state.gpr[30] = *entry;
|
||||
|
||||
// Run our apploader runner code
|
||||
ppc_state.pc = APPLOADER_RUNNER_BASE;
|
||||
|
||||
branch_watch.SetRecordingActive(guard, resume_branch_watch);
|
||||
// Blank out session key (https://debugmo.de/2008/05/part-2-dumping-the-media-board/)
|
||||
@ -238,6 +359,69 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard
|
||||
return true;
|
||||
}
|
||||
|
||||
void CBoot::HLEAppLoaderAfterEntry(const Core::CPUThreadGuard& guard)
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& mmu = system.GetMMU();
|
||||
|
||||
const u32 init = mmu.HostRead_U32(guard, APPLOADER_RUNNER_INIT_POINTER);
|
||||
const u32 main = mmu.HostRead_U32(guard, APPLOADER_RUNNER_MAIN_POINTER);
|
||||
const u32 close = mmu.HostRead_U32(guard, APPLOADER_RUNNER_CLOSE_POINTER);
|
||||
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, init, 0, "iAppLoaderInit", "Apploader");
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, main, 0, "iAppLoaderMain", "Apploader");
|
||||
system.GetPPCSymbolDB().AddKnownSymbol(guard, close, 0, "iAppLoaderClose", "Apploader");
|
||||
system.GetPPCSymbolDB().Index();
|
||||
Host_PPCSymbolsChanged();
|
||||
|
||||
INFO_LOG_FMT(BOOT, "Called iAppLoaderEntry; init at {:08x}, main at {:08x}, close at {:08x}",
|
||||
init, main, close);
|
||||
}
|
||||
|
||||
void CBoot::HLEAppLoaderAfterInit(const Core::CPUThreadGuard& guard)
|
||||
{
|
||||
INFO_LOG_FMT(BOOT, "Called iAppLoaderInit");
|
||||
}
|
||||
|
||||
void CBoot::HLEAppLoaderAfterMain(const Core::CPUThreadGuard& guard)
|
||||
{
|
||||
ASSERT(s_apploader_runner_state);
|
||||
if (!s_apploader_runner_state)
|
||||
return;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& mmu = system.GetMMU();
|
||||
|
||||
const u32 ram_address = mmu.HostRead_U32(guard, APPLOADER_RUNNER_MAIN_RAM_ADDR);
|
||||
const u32 length = mmu.HostRead_U32(guard, APPLOADER_RUNNER_MAIN_LENGTH);
|
||||
const u32 dvd_shift = s_apploader_runner_state->is_wii ? 2 : 0;
|
||||
const u32 dvd_offset = mmu.HostRead_U32(guard, APPLOADER_RUNNER_MAIN_DISC_OFFSET) << dvd_shift;
|
||||
|
||||
INFO_LOG_FMT(BOOT, "Called iAppLoaderMain; reading {:#x} bytes from disc offset {:08x} to {:08x}",
|
||||
length, dvd_offset, ram_address);
|
||||
DVDRead(system, s_apploader_runner_state->volume, dvd_offset, ram_address, length,
|
||||
s_apploader_runner_state->volume.GetGamePartition());
|
||||
DiscIO::Riivolution::ApplyApploaderMemoryPatches(
|
||||
guard, s_apploader_runner_state->riivolution_patches, ram_address, length);
|
||||
}
|
||||
|
||||
void CBoot::HLEAppLoaderAfterClose(const Core::CPUThreadGuard& guard)
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& ppc_state = system.GetPPCState();
|
||||
|
||||
INFO_LOG_FMT(BOOT, "Called iAppLoaderClose; game entry point is {:08x}", ppc_state.gpr[30]);
|
||||
SConfig::OnTitleDirectlyBooted(guard); // Clears the temporary patches and symbols
|
||||
}
|
||||
|
||||
void CBoot::HLEAppLoaderReport(std::string_view message)
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& ppc_state = system.GetPPCState();
|
||||
|
||||
INFO_LOG_FMT(BOOT, "AppLoader {:08x}->{:08x}| {}", LR(ppc_state), ppc_state.pc, message);
|
||||
}
|
||||
|
||||
void CBoot::SetupGCMemory(Core::System& system, const Core::CPUThreadGuard& guard)
|
||||
{
|
||||
auto& memory = system.GetMemory();
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
@ -34,6 +35,7 @@
|
||||
#include "Common/ScopeGuard.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/Version.h"
|
||||
|
||||
#include "Core/AchievementManager.h"
|
||||
@ -733,15 +735,17 @@ static std::string GenerateScreenshotFolderPath()
|
||||
return path;
|
||||
}
|
||||
|
||||
static std::string GenerateScreenshotName()
|
||||
static std::optional<std::string> GenerateScreenshotName()
|
||||
{
|
||||
// append gameId, path only contains the folder here.
|
||||
const std::string path_prefix =
|
||||
GenerateScreenshotFolderPath() + SConfig::GetInstance().GetGameID();
|
||||
|
||||
const std::time_t cur_time = std::time(nullptr);
|
||||
const std::string base_name =
|
||||
fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}", path_prefix, fmt::localtime(cur_time));
|
||||
const auto local_time = Common::LocalTime(cur_time);
|
||||
if (!local_time)
|
||||
return std::nullopt;
|
||||
const std::string base_name = fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}", path_prefix, *local_time);
|
||||
|
||||
// First try a filename without any suffixes, if already exists then append increasing numbers
|
||||
std::string name = fmt::format("{}.png", base_name);
|
||||
@ -757,7 +761,9 @@ static std::string GenerateScreenshotName()
|
||||
void SaveScreenShot()
|
||||
{
|
||||
const Core::CPUThreadGuard guard(Core::System::GetInstance());
|
||||
g_frame_dumper->SaveScreenshot(GenerateScreenshotName());
|
||||
std::optional<std::string> name = GenerateScreenshotName();
|
||||
if (name)
|
||||
g_frame_dumper->SaveScreenshot(*name);
|
||||
}
|
||||
|
||||
void SaveScreenShot(std::string_view name)
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
#include "Core/Boot/Boot.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/GeckoCode.h"
|
||||
@ -28,7 +29,7 @@ namespace HLE
|
||||
static std::map<u32, u32> s_hooked_addresses;
|
||||
|
||||
// clang-format off
|
||||
constexpr std::array<Hook, 23> os_patches{{
|
||||
constexpr std::array<Hook, 27> os_patches{{
|
||||
// Placeholder, os_patches[0] is the "non-existent function" index
|
||||
{"FAKE_TO_SKIP_0", HLE_Misc::UnimplementedFunction, HookType::Replace, HookFlag::Generic},
|
||||
|
||||
@ -60,7 +61,12 @@ constexpr std::array<Hook, 23> os_patches{{
|
||||
|
||||
{"GeckoCodehandler", HLE_Misc::GeckoCodeHandlerICacheFlush, HookType::Start, HookFlag::Fixed},
|
||||
{"GeckoHandlerReturnTrampoline", HLE_Misc::GeckoReturnTrampoline, HookType::Replace, HookFlag::Fixed},
|
||||
{"AppLoaderReport", HLE_OS::HLE_GeneralDebugPrint, HookType::Start, HookFlag::Fixed} // apploader needs OSReport-like function
|
||||
|
||||
{"HLEAppLoaderAfterEntry", CBoot::HLEAppLoaderAfterEntry, HookType::Start, HookFlag::Fixed},
|
||||
{"HLEAppLoaderAfterInit", CBoot::HLEAppLoaderAfterInit, HookType::Start, HookFlag::Fixed},
|
||||
{"HLEAppLoaderAfterMain", CBoot::HLEAppLoaderAfterMain, HookType::Start, HookFlag::Fixed},
|
||||
{"HLEAppLoaderAfterClose", CBoot::HLEAppLoaderAfterClose, HookType::Start, HookFlag::Fixed},
|
||||
{"HLEAppLoaderReport", HLE_OS::HLE_AppLoaderReport, HookType::Start, HookFlag::Fixed},
|
||||
}};
|
||||
// clang-format on
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/Boot/Boot.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HLE/HLE_VarArgs.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
@ -208,6 +209,14 @@ void HLE_LogVFPrint(const Core::CPUThreadGuard& guard)
|
||||
HLE_LogFPrint(guard, ParameterType::VariableArgumentList);
|
||||
}
|
||||
|
||||
// Version of OSReport used by Dolphin for running apploaders from BS2 HLE
|
||||
void HLE_AppLoaderReport(const Core::CPUThreadGuard& guard)
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
CBoot::HLEAppLoaderReport(
|
||||
SHIFTJISToUTF8(GetStringVA(system, guard, 3, ParameterType::ParameterList)));
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class HLEPrintArgsVAList final : public HLEPrintArgs
|
||||
|
@ -35,4 +35,5 @@ void HLE_LogDPrint(const Core::CPUThreadGuard& guard);
|
||||
void HLE_LogVDPrint(const Core::CPUThreadGuard& guard);
|
||||
void HLE_LogFPrint(const Core::CPUThreadGuard& guard);
|
||||
void HLE_LogVFPrint(const Core::CPUThreadGuard& guard);
|
||||
void HLE_AppLoaderReport(const Core::CPUThreadGuard& guard);
|
||||
} // namespace HLE_OS
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "Common/Network.h"
|
||||
#include "Common/PcapFile.h"
|
||||
#include "Common/ScopeGuard.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
@ -82,7 +83,7 @@ PCAPSSLCaptureLogger::PCAPSSLCaptureLogger()
|
||||
{
|
||||
const std::string filepath =
|
||||
fmt::format("{}{} {:%Y-%m-%d %Hh%Mm%Ss}.pcap", File::GetUserPath(D_DUMPSSL_IDX),
|
||||
SConfig::GetInstance().GetGameID(), fmt::localtime(std::time(nullptr)));
|
||||
SConfig::GetInstance().GetGameID(), *Common::LocalTime(std::time(nullptr)));
|
||||
m_file = std::make_unique<Common::PCAP>(
|
||||
new File::IOFile(filepath, "wb", File::SharedAccess::Read), Common::PCAP::LinkType::Ethernet);
|
||||
}
|
||||
|
@ -281,7 +281,7 @@ static std::string SystemTimeAsDoubleToString(double time)
|
||||
{
|
||||
// revert adjustments from GetSystemTimeAsDouble() to get a normal Unix timestamp again
|
||||
const time_t seconds = static_cast<time_t>(time) + DOUBLE_TIME_OFFSET;
|
||||
const auto local_time = Common::Localtime(seconds);
|
||||
const auto local_time = Common::LocalTime(seconds);
|
||||
if (!local_time)
|
||||
return "";
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/FrameDumpFFMpeg.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
|
||||
#if defined(__FreeBSD__)
|
||||
#define __STDC_CONSTANT_MACROS 1
|
||||
@ -124,11 +125,15 @@ std::string GetDumpPath(const std::string& extension, std::time_t time, u32 inde
|
||||
if (!dump_path.empty())
|
||||
return dump_path;
|
||||
|
||||
const auto local_time = Common::LocalTime(time);
|
||||
if (!local_time)
|
||||
return "";
|
||||
|
||||
const std::string path_prefix =
|
||||
File::GetUserPath(D_DUMPFRAMES_IDX) + SConfig::GetInstance().GetGameID();
|
||||
|
||||
const std::string base_name =
|
||||
fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}_{}", path_prefix, fmt::localtime(time), index);
|
||||
fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}_{}", path_prefix, *local_time, index);
|
||||
|
||||
const std::string path = fmt::format("{}.{}", base_name, extension);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user