Merge pull request #17725 from hrydgard/custom-achievement-sound-effects

Custom achievement sound effects
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2023-07-16 15:44:08 +02:00
commit 755269c90d
22 files changed
+302 -79

No files matched your search

+2 -1
View File
@@ -4,6 +4,7 @@
#include "Common/File/VFS/VFS.h"
#include "Common/File/FileUtil.h"
#include "Common/File/AndroidStorage.h"
#include "Common/StringUtils.h"
VFS g_VFS;
@@ -27,7 +28,7 @@ void VFS::Clear() {
static bool IsLocalAbsolutePath(const char *path) {
bool isUnixLocal = path[0] == '/';
#ifdef _WIN32
bool isWindowsLocal = isalpha(path[0]) && path[1] == ':';
bool isWindowsLocal = (isalpha(path[0]) && path[1] == ':') || startsWith(path, "\\\\") || startsWith(path, "//");
#else
bool isWindowsLocal = false;
#endif
+1
View File
@@ -90,6 +90,7 @@ enum class BrowseFileType {
IMAGE,
INI,
DB,
SOUND_EFFECT,
ANY,
};
-10
View File
@@ -51,16 +51,6 @@ public:
IconCacheStats GetStats();
// for testing
std::string GetFirstIconName() const {
if (!cache_.empty()) {
return cache_.begin()->first;
} else if (!pending_.empty()) {
return *pending_.begin();
}
return "";
}
private:
void Decimate(int64_t maxSize);
+25 -1
View File
@@ -597,7 +597,7 @@ void AbstractChoiceWithValueDisplay::Draw(UIContext &dc) {
std::string valueText = ValueText();
if (password_) {
if (passwordDisplay_) {
// Replace all characters with stars.
memset(&valueText[0], '*', valueText.size());
}
@@ -654,4 +654,28 @@ std::string ChoiceWithValueDisplay::ValueText() const {
return valueText.str();
}
FileChooserChoice::FileChooserChoice(std::string *value, const std::string &text, BrowseFileType fileType, LayoutParams *layoutParams)
: AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), fileType_(fileType) {
OnClick.Add([=](UI::EventParams &) {
System_BrowseForFile(text_, fileType, [=](const std::string &returnValue, int) {
if (*value_ != returnValue) {
*value = returnValue;
UI::EventParams e{};
e.s = *value;
OnChange.Trigger(e);
}
});
return UI::EVENT_DONE;
});
}
std::string FileChooserChoice::ValueText() const {
if (value_->empty()) {
auto di = GetI18NCategory(I18NCat::DIALOG);
return di->T("Default");
}
Path path(*value_);
return path.GetFilename();
}
} // namespace
+20 -2
View File
@@ -1,5 +1,7 @@
#pragma once
#include "Common/System/Request.h"
#include "Common/Data/Text/I18n.h"
#include "Common/UI/UIScreen.h"
#include "Common/UI/UI.h"
@@ -192,7 +194,7 @@ public:
void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override;
void SetPasswordDisplay() {
password_ = true;
passwordDisplay_ = true;
}
protected:
@@ -200,7 +202,7 @@ protected:
float CalculateValueScale(const UIContext &dc, const std::string &valueText, float availWidth) const;
bool password_ = false;
bool passwordDisplay_ = false;
};
// Reads and writes value to determine the current selection.
@@ -407,4 +409,20 @@ private:
std::string(*translateCallback_)(const char *value) = nullptr;
};
enum class FileChooserFileType {
WAVE_FILE,
};
class FileChooserChoice : public AbstractChoiceWithValueDisplay {
public:
FileChooserChoice(std::string *value, const std::string &title, BrowseFileType fileType, LayoutParams *layoutParams = nullptr);
std::string ValueText() const override;
Event OnChange;
private:
std::string *value_;
BrowseFileType fileType_;
};
} // namespace UI
+1 -2
View File
@@ -141,8 +141,7 @@ static void MoveFocus(ViewGroup *root, FocusDirection direction) {
return;
}
NeighborResult neigh = root->FindNeighbor(focusedView, direction, neigh);
NeighborResult neigh = root->FindNeighbor(focusedView, direction, NeighborResult());
if (neigh.view) {
neigh.view->SetFocus();
root->SubviewFocused(neigh.view);
+6 -1
View File
@@ -265,11 +265,11 @@ static bool DefaultSasThread() {
}
static const ConfigSetting achievementSettings[] = {
// Core settings
ConfigSetting("AchievementsEnable", &g_Config.bAchievementsEnable, true, CfgFlag::DEFAULT),
ConfigSetting("AchievementsChallengeMode", &g_Config.bAchievementsChallengeMode, false, CfgFlag::DEFAULT),
ConfigSetting("AchievementsEncoreMode", &g_Config.bAchievementsEncoreMode, false, CfgFlag::DEFAULT),
ConfigSetting("AchievementsUnofficial", &g_Config.bAchievementsUnofficial, false, CfgFlag::DEFAULT),
ConfigSetting("AchievementsSoundEffects", &g_Config.bAchievementsSoundEffects, true, CfgFlag::DEFAULT),
ConfigSetting("AchievementsLogBadMemReads", &g_Config.bAchievementsLogBadMemReads, false, CfgFlag::DEFAULT),
// Achievements login info. Note that password is NOT stored, only a login token.
@@ -277,6 +277,11 @@ static const ConfigSetting achievementSettings[] = {
// from the ini if manually entered (useful when testing various builds on Android).
ConfigSetting("AchievementsToken", &g_Config.sAchievementsToken, "", CfgFlag::DONT_SAVE),
ConfigSetting("AchievementsUserName", &g_Config.sAchievementsUserName, "", CfgFlag::DEFAULT),
// Customizations
ConfigSetting("AchievementsSoundEffects", &g_Config.bAchievementsSoundEffects, true, CfgFlag::DEFAULT),
ConfigSetting("AchievementsUnlockAudioFile", &g_Config.sAchievementsUnlockAudioFile, "", CfgFlag::DEFAULT),
ConfigSetting("AchievementsLeaderboardSubmitAudioFile", &g_Config.sAchievementsLeaderboardSubmitAudioFile, "", CfgFlag::DEFAULT),
};
static const ConfigSetting cpuSettings[] = {
+4
View File
@@ -495,6 +495,10 @@ public:
bool bAchievementsSoundEffects;
bool bAchievementsLogBadMemReads;
// Customizations
std::string sAchievementsUnlockAudioFile;
std::string sAchievementsLeaderboardSubmitAudioFile;
// Achivements login info. Note that password is NOT stored, only a login token.
// Still, we may wanna store it more securely than in PPSSPP.ini, especially on Android.
std::string sAchievementsUserName;
+3
View File
@@ -312,6 +312,9 @@ bool MainUI::HandleCustomEvent(QEvent *e) {
case BrowseFileType::DB:
filter = "DB files (*.db)";
break;
case BrowseFileType::SOUND_EFFECT:
filter = "WAVE files (*.wav)";
break;
case BrowseFileType::ANY:
break;
}
+2 -1
View File
@@ -244,7 +244,8 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
}
};
DarwinFileSystemServices services;
services.presentDirectoryPanel(callback, /* allowFiles = */ true, /* allowDirectories = */ false);
BrowseFileType fileType = (BrowseFileType)param3;
services.presentDirectoryPanel(callback, /* allowFiles = */ true, /* allowDirectories = */ false, fileType);
return true;
}
case SystemRequestType::BROWSE_FOR_FOLDER:
+99 -35
View File
@@ -4,18 +4,20 @@
#include "Common/File/VFS/VFS.h"
#include "Common/UI/Root.h"
#include "Common/Data/Text/I18n.h"
#include "Common/CommonTypes.h"
#include "Common/Data/Format/RIFF.h"
#include "Common/Log.h"
#include "Common/System/System.h"
#include "Common/System/OSD.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/TimeUtil.h"
#include "Common/Data/Collections/FixedSizeQueue.h"
#include "Core/HW/SimpleAudioDec.h"
#include "Core/HLE/__sceAudio.h"
#include "Core/System.h"
#include "GameInfoCache.h"
#include "Core/Config.h"
#include "UI/GameInfoCache.h"
#include "UI/BackgroundAudio.h"
struct WavData {
@@ -367,6 +369,44 @@ void BackgroundAudio::Update() {
}
}
inline int16_t ConvertU8ToI16(uint8_t value) {
int ivalue = value - 128;
return ivalue * 255;
}
Sample *Sample::Load(const std::string &path) {
size_t bytes;
uint8_t *data = g_VFS.ReadFile(path.c_str(), &bytes);
if (!data) {
WARN_LOG(AUDIO, "Failed to load sample '%s'", path.c_str());
return nullptr;
}
RIFFReader reader(data, (int)bytes);
WavData wave;
wave.Read(reader);
delete[] data;
if (wave.num_channels > 2 || wave.raw_bytes_per_frame > sizeof(int16_t) * wave.num_channels) {
ERROR_LOG(AUDIO, "Wave format not supported for mixer playback. Must be 8-bit or 16-bit raw mono or stereo. '%s'", path.c_str());
return nullptr;
}
int16_t *samples = new int16_t[wave.num_channels * wave.numFrames];
if (wave.raw_bytes_per_frame == wave.num_channels * 2) {
// 16-bit
memcpy(samples, wave.raw_data, wave.numFrames * wave.raw_bytes_per_frame);
} else if (wave.raw_bytes_per_frame == wave.num_channels) {
// 8-bit. Convert.
for (int i = 0; i < wave.num_channels * wave.numFrames; i++) {
samples[i] = ConvertU8ToI16(wave.raw_data[i]);
}
}
return new Sample(samples, wave.num_channels, wave.numFrames, wave.sample_rate);
}
static inline int16_t Clamp16(int32_t sample) {
if (sample < -32767) return -32767;
if (sample > 32767) return 32767;
@@ -407,15 +447,25 @@ void SoundEffectMixer::Mix(int16_t *buffer, int sz, int sampleRateHz) {
int wholeOffset = iter->offset >> 32;
int frac = (iter->offset >> 20) & 0xFFF; // Use a 12 bit fraction to get away with 32-bit multiplies
int interpolatedLeft = (sample->data_[wholeOffset * 2] * (0x1000 - frac) + sample->data_[(wholeOffset + 1) * 2] * frac) >> 12;
int interpolatedRight = (sample->data_[wholeOffset * 2 + 1] * (0x1000 - frac) + sample->data_[(wholeOffset + 1) * 2 + 1] * frac) >> 12;
if (sample->channels_ == 2) {
int interpolatedLeft = (sample->data_[wholeOffset * 2] * (0x1000 - frac) + sample->data_[(wholeOffset + 1) * 2] * frac) >> 12;
int interpolatedRight = (sample->data_[wholeOffset * 2 + 1] * (0x1000 - frac) + sample->data_[(wholeOffset + 1) * 2 + 1] * frac) >> 12;
// Clamping add on top per sample. Not great, we should be mixing at higher bitrate instead. Oh well.
int left = Clamp16(buffer[i] + (interpolatedLeft * iter->volume >> 8));
int right = Clamp16(buffer[i + 1] + (interpolatedRight * iter->volume >> 8));
// Clamping add on top per sample. Not great, we should be mixing at higher bitrate instead. Oh well.
int left = Clamp16(buffer[i] + (interpolatedLeft * iter->volume >> 8));
int right = Clamp16(buffer[i + 1] + (interpolatedRight * iter->volume >> 8));
buffer[i] = left;
buffer[i + 1] = right;
buffer[i] = left;
buffer[i + 1] = right;
} else if (sample->channels_ == 1) {
int interpolated = (sample->data_[wholeOffset] * (0x1000 - frac) + sample->data_[wholeOffset + 1] * frac) >> 12;
// Clamping add on top per sample. Not great, we should be mixing at higher bitrate instead. Oh well.
int value = Clamp16(buffer[i] + (interpolated * iter->volume >> 8));
buffer[i] = value;
buffer[i + 1] = value;
}
iter->offset += stride;
}
@@ -433,40 +483,54 @@ void SoundEffectMixer::Play(UI::UISound sfx, float volume) {
queue_.push_back(PlayInstance{ sfx, 0, (int)(255.0f * volume), false });
}
Sample *SoundEffectMixer::LoadSample(const std::string &path) {
size_t bytes;
uint8_t *data = g_VFS.ReadFile(path.c_str(), &bytes);
if (!data) {
WARN_LOG(AUDIO, "Failed to load sample '%s'", path.c_str());
return nullptr;
void SoundEffectMixer::UpdateSample(UI::UISound sound, Sample *sample) {
if (sample) {
std::lock_guard<std::mutex> guard(mutex_);
samples_[(size_t)sound] = std::unique_ptr<Sample>(sample);
} else {
LoadDefaultSample(sound);
}
}
RIFFReader reader(data, (int)bytes);
WavData wave;
wave.Read(reader);
delete[] data;
if (wave.num_channels != 2 || wave.raw_bytes_per_frame != 4) {
ERROR_LOG(AUDIO, "Wave format not supported for mixer playback. Must be 16-bit raw stereo. '%s'", path.c_str());
return nullptr;
void SoundEffectMixer::LoadDefaultSample(UI::UISound sound) {
const char *filename = nullptr;
switch (sound) {
case UI::UISound::BACK: filename = "sfx_back.wav"; break;
case UI::UISound::SELECT: filename = "sfx_select.wav"; break;
case UI::UISound::CONFIRM: filename = "sfx_confirm.wav"; break;
case UI::UISound::TOGGLE_ON: filename = "sfx_toggle_on.wav"; break;
case UI::UISound::TOGGLE_OFF: filename = "sfx_toggle_off.wav"; break;
case UI::UISound::ACHIEVEMENT_UNLOCKED: filename = "sfx_achievement_unlocked.wav"; break;
case UI::UISound::LEADERBOARD_SUBMITTED: filename = "sfx_leaderbord_submitted.wav"; break;
default:
return;
}
int16_t *samples = new int16_t[2 * wave.numFrames];
memcpy(samples, wave.raw_data, wave.numFrames * wave.raw_bytes_per_frame);
return new Sample(samples, wave.numFrames, wave.sample_rate);
Sample *sample = Sample::Load(filename);
if (!sample) {
ERROR_LOG(SYSTEM, "Failed to load the default sample for UI sound %d", (int)sound);
}
std::lock_guard<std::mutex> guard(mutex_);
samples_[(size_t)sound] = std::unique_ptr<Sample>(sample);
}
void SoundEffectMixer::LoadSamples() {
samples_.resize((size_t)UI::UISound::COUNT);
samples_[(size_t)UI::UISound::BACK] = std::unique_ptr<Sample>(LoadSample("sfx_back.wav"));
samples_[(size_t)UI::UISound::SELECT] = std::unique_ptr<Sample>(LoadSample("sfx_select.wav"));
samples_[(size_t)UI::UISound::CONFIRM] = std::unique_ptr<Sample>(LoadSample("sfx_confirm.wav"));
samples_[(size_t)UI::UISound::TOGGLE_ON] = std::unique_ptr<Sample>(LoadSample("sfx_toggle_on.wav"));
samples_[(size_t)UI::UISound::TOGGLE_OFF] = std::unique_ptr<Sample>(LoadSample("sfx_toggle_off.wav"));
samples_[(size_t)UI::UISound::ACHIEVEMENT_UNLOCKED] = std::unique_ptr<Sample>(LoadSample("sfx_achievement_unlocked.wav"));
samples_[(size_t)UI::UISound::LEADERBOARD_SUBMITTED] = std::unique_ptr<Sample>(LoadSample("sfx_leaderbord_submitted.wav"));
LoadDefaultSample(UI::UISound::BACK);
LoadDefaultSample(UI::UISound::SELECT);
LoadDefaultSample(UI::UISound::CONFIRM);
LoadDefaultSample(UI::UISound::TOGGLE_ON);
LoadDefaultSample(UI::UISound::TOGGLE_OFF);
if (!g_Config.sAchievementsUnlockAudioFile.empty()) {
UpdateSample(UI::UISound::ACHIEVEMENT_UNLOCKED, Sample::Load(g_Config.sAchievementsUnlockAudioFile));
} else {
LoadDefaultSample(UI::UISound::ACHIEVEMENT_UNLOCKED);
}
if (!g_Config.sAchievementsLeaderboardSubmitAudioFile.empty()) {
UpdateSample(UI::UISound::LEADERBOARD_SUBMITTED, Sample::Load(g_Config.sAchievementsLeaderboardSubmitAudioFile));
} else {
LoadDefaultSample(UI::UISound::LEADERBOARD_SUBMITTED);
}
UI::SetSoundCallback([](UI::UISound sound, float volume) {
g_BackgroundAudio.SFX().Play(sound, volume);
+9 -3
View File
@@ -12,24 +12,29 @@ class AT3PlusReader;
struct Sample {
// data must be new-ed.
Sample(int16_t *data, int length, int rateInHz) : data_(data), length_(length), rateInHz_(rateInHz) {}
Sample(int16_t *data, int channels, int length, int rateInHz) : channels_(channels), data_(data), length_(length), rateInHz_(rateInHz) {}
~Sample() {
delete[] data_;
}
int16_t *data_;
int length_; // stereo samples.
int length_; // stereo or mono samples.
int rateInHz_; // sampleRate
int channels_;
static Sample *Load(const std::string &path);
};
// Mixer for things played on top of everything.
class SoundEffectMixer {
public:
static Sample *LoadSample(const std::string &path);
void LoadSamples();
void Mix(int16_t *buffer, int sz, int sampleRateHz);
void Play(UI::UISound sfx, float volume);
void UpdateSample(UI::UISound sound, Sample *sample);
void LoadDefaultSample(UI::UISound sound);
std::vector<std::unique_ptr<Sample>> samples_;
struct PlayInstance {
@@ -39,6 +44,7 @@ public:
bool done;
};
private:
std::mutex mutex_;
std::vector<PlayInstance> queue_;
std::vector<PlayInstance> plays_;
+13 -10
View File
@@ -9,6 +9,7 @@
#include "ppsspp_config.h"
#include "Common/File/Path.h"
#include "Common/System/Request.h"
#define PreferredMemoryStickUserDefaultsKey "UserPreferredMemoryStickDirectoryPath"
@@ -18,18 +19,20 @@ typedef std::function<void (bool, Path)> DarwinDirectoryPanelCallback;
/// on Darwin platforms.
class DarwinFileSystemServices {
public:
/// Present a pannel to choose the directory as the memory stick manager.
void presentDirectoryPanel(DarwinDirectoryPanelCallback,
bool allowFiles = false,
bool allowDirectories = true);
static Path appropriateMemoryStickDirectoryToUse();
static void setUserPreferredMemoryStickDirectory(Path);
/// Present a panel to choose a file or directory.
void presentDirectoryPanel(
DarwinDirectoryPanelCallback,
bool allowFiles = false,
bool allowDirectories = true,
BrowseFileType fileType = BrowseFileType::ANY);
static Path appropriateMemoryStickDirectoryToUse();
static void setUserPreferredMemoryStickDirectory(Path);
private:
static Path __defaultMemoryStickPath();
static Path __defaultMemoryStickPath();
#if PPSSPP_PLATFORM(IOS)
// iOS only, needed for UIDocumentPickerViewController
void *__pickerDelegate = NULL;
// iOS only, needed for UIDocumentPickerViewController
void *__pickerDelegate = NULL;
#endif // PPSSPP_PLATFORM(IOS)
};
+28 -8
View File
@@ -44,15 +44,35 @@
#include <AppKit/AppKit.h>
#endif // __has_include(<UIKit/UIKit.h>)
void DarwinFileSystemServices::presentDirectoryPanel(DarwinDirectoryPanelCallback callback,
bool allowFiles,
bool allowDirectories) {
dispatch_async(dispatch_get_main_queue(), ^{
void DarwinFileSystemServices::presentDirectoryPanel(
DarwinDirectoryPanelCallback callback,
bool allowFiles, bool allowDirectories,
BrowseFileType fileType) {
dispatch_async(dispatch_get_main_queue(), ^{
#if PPSSPP_PLATFORM(MAC)
NSOpenPanel *panel = [[NSOpenPanel alloc] init];
panel.allowsMultipleSelection = NO;
panel.canChooseFiles = allowFiles;
panel.canChooseDirectories = allowDirectories;
NSOpenPanel *panel = [[NSOpenPanel alloc] init];
panel.allowsMultipleSelection = NO;
panel.canChooseFiles = allowFiles;
panel.canChooseDirectories = allowDirectories;
switch (fileType) {
case BrowseFileType::BOOTABLE:
[panel setAllowedFileTypes:[NSArray arrayWithObjects:@"iso", @"cso", @"pbp", @"elf", @"zip", @"ppdmp", nil]];
break;
case BrowseFileType::IMAGE:
[panel setAllowedFileTypes:[NSArray arrayWithObjects:@"jpg", @"png", nil]];
break;
case BrowseFileType::INI:
[panel setAllowedFileTypes:[NSArray arrayWithObject:@"ini"]];
break;
case BrowseFileType::DB:
[panel setAllowedFileTypes:[NSArray arrayWithObject:@"db"]];
break;
case BrowseFileType::SOUND_EFFECT:
[panel setAllowedFileTypes:[NSArray arrayWithObject:@"wav"]];
break;
default:
break;
}
// if (!allowFiles && allowDirectories)
// panel.allowedFileTypes = @[(__bridge NSString *)kUTTypeFolder];
+60 -1
View File
@@ -1,4 +1,3 @@
#include "UI/RetroAchievementScreens.h"
#include "Common/System/OSD.h"
#include "Common/System/Request.h"
#include "Common/UI/View.h"
@@ -10,10 +9,57 @@
#include "Core/Config.h"
#include "Core/RetroAchievements.h"
#include "UI/RetroAchievementScreens.h"
#include "UI/BackgroundAudio.h"
static inline const char *DeNull(const char *ptr) {
return ptr ? ptr : "";
}
// Compound view, creating a FileChooserChoice inside.
class AudioFileChooser : public UI::LinearLayout {
public:
AudioFileChooser(std::string *value, const std::string &title, UI::UISound sound, UI::LayoutParams *layoutParams = nullptr);
UI::UISound sound_;
};
static constexpr UI::Size ITEM_HEIGHT = 64.f;
AudioFileChooser::AudioFileChooser(std::string *value, const std::string &title, UI::UISound sound, UI::LayoutParams *layoutParams) : UI::LinearLayout(UI::ORIENT_HORIZONTAL, layoutParams), sound_(sound) {
using namespace UI;
SetSpacing(2.0f);
if (!layoutParams) {
layoutParams_->width = FILL_PARENT;
layoutParams_->height = ITEM_HEIGHT;
}
Add(new FileChooserChoice(value, title, BrowseFileType::SOUND_EFFECT, new LinearLayoutParams(1.0f)))->OnChange.Add([=](UI::EventParams &e) {
// TODO: Check the file format here.
// Need to forward the event out.
std::string path = e.s;
Sample *sample = Sample::Load(path);
if (sample) {
g_BackgroundAudio.SFX().UpdateSample(sound, sample);
} else {
if (!sample) {
auto au = GetI18NCategory(I18NCat::AUDIO);
g_OSD.Show(OSDType::MESSAGE_WARNING, au->T("Audio file format not supported. Must be 16-bit WAV."));
}
value->clear();
}
return UI::EVENT_DONE;
});
Add(new Choice(ImageID("I_ARROW_RIGHT"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) {
g_BackgroundAudio.SFX().Play(sound_, 0.6f);
return UI::EVENT_DONE;
});
Add(new Choice(ImageID("I_TRASHCAN"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) {
g_BackgroundAudio.SFX().UpdateSample(sound, nullptr);
value->clear();
return UI::EVENT_DONE;
});
}
void RetroAchievementsListScreen::CreateTabs() {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
@@ -194,6 +240,10 @@ void RetroAchievementsSettingsScreen::CreateTabs() {
using namespace UI;
CreateAccountTab(AddTab("AchievementsAccount", ac->T("Account")));
if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) {
// Don't bother creating this tab if we don't have a file browser.
CreateCustomizeTab(AddTab("AchievementsCustomize", ac->T("Customize")));
}
CreateDeveloperToolsTab(AddTab("AchievementsDeveloperTools", sy->T("Developer Tools")));
}
@@ -283,6 +333,15 @@ void RetroAchievementsSettingsScreen::CreateAccountTab(UI::ViewGroup *viewGroup)
});
}
void RetroAchievementsSettingsScreen::CreateCustomizeTab(UI::ViewGroup *viewGroup) {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
using namespace UI;
viewGroup->Add(new ItemHeader(ac->T("Sound effects")));
viewGroup->Add(new AudioFileChooser(&g_Config.sAchievementsUnlockAudioFile, "Achievement unlocked", UISound::ACHIEVEMENT_UNLOCKED));
viewGroup->Add(new AudioFileChooser(&g_Config.sAchievementsLeaderboardSubmitAudioFile, "Leaderboard score submission", UISound::LEADERBOARD_SUBMITTED));
}
void RetroAchievementsSettingsScreen::CreateDeveloperToolsTab(UI::ViewGroup *viewGroup) {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
+1
View File
@@ -42,6 +42,7 @@ protected:
private:
void CreateAccountTab(UI::ViewGroup *viewGroup);
void CreateCustomizeTab(UI::ViewGroup *viewGroup);
void CreateDeveloperToolsTab(UI::ViewGroup *viewGroup);
std::string username_;
+3
View File
@@ -488,6 +488,9 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
case BrowseFileType::DB:
picker->FileTypeFilter->Append(".db");
break;
case BrowseFileType::SOUND_EFFECT:
picker->FileTypeFilter->Append(".wav");
break;
case BrowseFileType::ANY:
picker->FileTypeFilter->Append("*");
break;
+3
View File
@@ -537,6 +537,9 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
case BrowseFileType::DB:
filter = MakeFilter(L"Cheat db files (*.db)|*.db|All files (*.*)|*.*||");
break;
case BrowseFileType::SOUND_EFFECT:
filter = MakeFilter(L"WAVE files (*.wav)|*.wav|All files (*.*)|*.*||");
break;
case BrowseFileType::ANY:
filter = MakeFilter(L"All files (*.*)|*.*||");
break;
+11 -1
View File
@@ -1066,8 +1066,18 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
PushCommand("browse_image", StringFromFormat("%d", requestId));
return true;
case SystemRequestType::BROWSE_FOR_FILE:
PushCommand("browse_file", StringFromFormat("%d", requestId));
{
BrowseFileType fileType = (BrowseFileType)param3;
switch (fileType) {
case BrowseFileType::SOUND_EFFECT:
PushCommand("browse_file_audio", StringFromFormat("%d", requestId));
break;
default:
PushCommand("browse_file", StringFromFormat("%d", requestId));
break;
}
return true;
}
case SystemRequestType::BROWSE_FOR_FOLDER:
PushCommand("browse_folder", StringFromFormat("%d", requestId));
return true;
@@ -1373,14 +1373,18 @@ public abstract class NativeActivity extends Activity {
Log.e(TAG, e.toString());
return false;
}
} else if (command.equals("browse_file")) {
} else if (command.equals("browse_file") || command.equals("browse_file_audio")) {
try {
int requestId = Integer.parseInt(params);
int packedResultCode = packResultCode(RESULT_OPEN_DOCUMENT, requestId);
Log.i(TAG, "browse_file request ID: " + requestId + " packed: " + packedResultCode);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
if (command.equals("browse_file_audio")) {
intent.setType("audio/x-wav");
} else {
intent.setType("*/*");
}
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
// Possible alternative approach:
// String[] mimeTypes = {"application/octet-stream", "/x-iso9660-image"};
+4
View File
@@ -4,12 +4,15 @@
%1: Leaderboard attempt failed = %1: Topplisteförsök misslyckades
%1: Submitting leaderboard score: %2! = %1: Skickar in toppliste-poäng: %2!
Account = Inloggning
Achievement Unlocked = Achievement Unlocked
Achievements = Achievements
Challenge Mode = Utmanings-läge
Challenge Mode (no savestates) = Utmanings-läge (inga sparade state)
Customize = Customize
Earned = Du har tjänat %d av %d achievements, och %d of %d poäng
Failed to log in, check your username and password. = Misslyckades att logga in, kontrollera ditt användarnamn och lösenord.
How to use RetroAchievements = Hur man använder RetroAchievements
Leaderboard score submission = Leaderboard score submission
Leaderboard submission is enabled = Skickar in poäng till ledartabeller
Leaderboards = Leaderboards
Links = Links
@@ -317,6 +320,7 @@ ConnectingAP = Kopplar upp till acceesspunkten.\nVänta...
ConnectingPleaseWait = Kopplar upp.\nVänta...
ConnectionName = Uppkopplingsnamn
Corrupted Data = Korrupt data
Default = Default
Delete = Radera
Delete all = Radera allt
Delete completed = Borttaget.