Compare commits

...

24 Commits

Author SHA1 Message Date
David Marcec
f9d8f20d7e fixed size calculation error for decode interleaved 2018-07-29 12:19:17 +10:00
David Marcec
f6e5fb17d7 removed externals/ from the submodule name 2018-07-29 10:57:30 +10:00
David Marcec
fd53a7b0a1 string->cstring and made WorkerBufferSize a static local function 2018-07-29 10:33:56 +10:00
David Marcec
d2e361533c const 2018-07-29 10:24:38 +10:00
David Marcec
afa72e1725 Use struct deleter instead 2018-07-29 10:19:23 +10:00
David Marcec
86c41aabd7 Fixed opus cmake, changed std::unique_ptr 2018-07-29 09:44:58 +10:00
David Marcec
a7c8064927 Used unique_ptr
unique_ptr doesn't seem to like c structs, so using a void * and casting it to an OpusDecoder
2018-07-29 01:38:39 +10:00
David Marcec
ade5096f84 Added new hwopus commands 2018-07-29 00:41:21 +10:00
bunnei
e1d66ea794 Merge pull request #842 from bunnei/audio-core
Initial implementation of Audio Core
2018-07-27 20:25:32 -07:00
bunnei
f1c519f2cb audout: Implement IAudioOut interface with AudioCore. 2018-07-27 22:55:39 -04:00
bunnei
2a742229ee core: Add AudioCore to global state. 2018-07-27 22:33:37 -04:00
bunnei
ab756fd068 audio_core: Add initial code for keeping track of audout state. 2018-07-27 22:33:31 -04:00
bunnei
abb4894188 Merge pull request #696 from DarkLordZach/romfs
RomFS Extraction
2018-07-27 19:10:05 -07:00
Zach Hilman
906d785c73 RomFS Extraction 2018-07-27 18:14:03 -04:00
bunnei
0191a1e526 Merge pull request #845 from lioncash/nfc
service: Add nfc services
2018-07-27 14:29:27 -07:00
bunnei
2128ab2d21 Merge pull request #839 from FearlessTobi/actually-port-3594
Port #3594 from Citra: "citra_qt: Add Continue/Pause & Toggle Speed Limit hotkeys"
2018-07-27 13:06:56 -07:00
bunnei
833ebbb626 Merge pull request #844 from lioncash/lbl
service: Add the lbl service
2018-07-27 13:06:11 -07:00
bunnei
d2749ef0ed Merge pull request #841 from lioncash/btdrv
service: Add the btdrv service
2018-07-27 13:05:36 -07:00
Lioncash
50dadc33e3 service/nfc: Implement Create[x]Interface functions
These simply return the respective interface.
2018-07-27 15:12:08 -04:00
Lioncash
04d144aa40 service: Add nfc services
Adds the skeleton of the nfc service based off the information provided
on Switch Brew.
2018-07-27 14:50:24 -04:00
Lioncash
ea8dd8b650 service/lbl: Implement EnableVrMode, DisableVrMode and GetVrMode
Implements these functions according to the information available on
Switch Brew.
2018-07-27 14:20:42 -04:00
Lioncash
c2c543e8f7 service: Add the lbl service
Adds the skeleton of the lbl service based off the information provided
by Switch Brew.
2018-07-27 14:20:07 -04:00
Lioncash
f49248437e service: Add the btdrv service
Adds the skeleton for the btdrv service based off the information provided by Switch Brew
2018-07-26 18:06:17 -04:00
fearlessTobi
18c2c96927 Port #3594 from Citra 2018-07-26 16:09:52 +02:00
38 changed files with 1414 additions and 122 deletions

3
.gitmodules vendored
View File

@@ -22,3 +22,6 @@
[submodule "unicorn"]
path = externals/unicorn
url = https://github.com/yuzu-emu/unicorn
[submodule "opus"]
path = externals/opus
url = https://github.com/ogniK5377/opus.git

View File

@@ -50,3 +50,7 @@ if (ARCHITECTURE_x86_64)
target_include_directories(xbyak INTERFACE ./xbyak/xbyak)
target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES)
endif()
# Opus
add_subdirectory(opus)
target_include_directories(opus INTERFACE ./opus/include)

1
externals/opus vendored Submodule

Submodule externals/opus added at b2871922a1

View File

@@ -3,6 +3,7 @@ include_directories(.)
add_subdirectory(common)
add_subdirectory(core)
add_subdirectory(audio_core)
add_subdirectory(video_core)
add_subdirectory(input_common)
add_subdirectory(tests)

View File

@@ -0,0 +1,11 @@
add_library(audio_core STATIC
audio_out.cpp
audio_out.h
buffer.h
stream.cpp
stream.h
)
create_target_directory_groups(audio_core)
target_link_libraries(audio_core PUBLIC common core)

View File

@@ -0,0 +1,50 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/audio_out.h"
#include "common/assert.h"
#include "common/logging/log.h"
namespace AudioCore {
/// Returns the stream format from the specified number of channels
static Stream::Format ChannelsToStreamFormat(int num_channels) {
switch (num_channels) {
case 1:
return Stream::Format::Mono16;
case 2:
return Stream::Format::Stereo16;
case 6:
return Stream::Format::Multi51Channel16;
}
LOG_CRITICAL(Audio, "Unimplemented num_channels={}", num_channels);
UNREACHABLE();
return {};
}
StreamPtr AudioOut::OpenStream(int sample_rate, int num_channels,
Stream::ReleaseCallback&& release_callback) {
streams.push_back(std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels),
std::move(release_callback)));
return streams.back();
}
std::vector<u64> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) {
return stream->GetTagsAndReleaseBuffers(max_count);
}
void AudioOut::StartStream(StreamPtr stream) {
stream->Play();
}
void AudioOut::StopStream(StreamPtr stream) {
stream->Stop();
}
bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data) {
return stream->QueueBuffer(std::make_shared<Buffer>(tag, std::move(data)));
}
} // namespace AudioCore

View File

@@ -0,0 +1,44 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <vector>
#include "audio_core/buffer.h"
#include "audio_core/stream.h"
#include "common/common_types.h"
namespace AudioCore {
using StreamPtr = std::shared_ptr<Stream>;
/**
* Represents an audio playback interface, used to open and play audio streams
*/
class AudioOut {
public:
/// Opens a new audio stream
StreamPtr OpenStream(int sample_rate, int num_channels,
Stream::ReleaseCallback&& release_callback);
/// Returns a vector of recently released buffers specified by tag for the specified stream
std::vector<u64> GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count);
/// Starts an audio stream for playback
void StartStream(StreamPtr stream);
/// Stops an audio stream that is currently playing
void StopStream(StreamPtr stream);
/// Queues a buffer into the specified audio stream, returns true on success
bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data);
private:
/// Active audio streams on the interface
std::vector<StreamPtr> streams;
};
} // namespace AudioCore

37
src/audio_core/buffer.h Normal file
View File

@@ -0,0 +1,37 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "common/common_types.h"
namespace AudioCore {
/**
* Represents a buffer of audio samples to be played in an audio stream
*/
class Buffer {
public:
using Tag = u64;
Buffer(Tag tag, std::vector<u8>&& data) : tag{tag}, data{std::move(data)} {}
/// Returns the raw audio data for the buffer
const std::vector<u8>& GetData() const {
return data;
}
/// Returns the buffer tag, this is provided by the game to the audout service
Tag GetTag() const {
return tag;
}
private:
Tag tag;
std::vector<u8> data;
};
} // namespace AudioCore

103
src/audio_core/stream.cpp Normal file
View File

@@ -0,0 +1,103 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "audio_core/stream.h"
namespace AudioCore {
constexpr size_t MaxAudioBufferCount{32};
/// Returns the sample size for the specified audio stream format
static size_t SampleSizeFromFormat(Stream::Format format) {
switch (format) {
case Stream::Format::Mono16:
return 2;
case Stream::Format::Stereo16:
return 4;
case Stream::Format::Multi51Channel16:
return 12;
};
LOG_CRITICAL(Audio, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
return {};
}
Stream::Stream(int sample_rate, Format format, ReleaseCallback&& release_callback)
: sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)} {
release_event = CoreTiming::RegisterEvent(
"Stream::Release", [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
}
void Stream::Play() {
state = State::Playing;
PlayNextBuffer();
}
void Stream::Stop() {
ASSERT_MSG(false, "Unimplemented");
}
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
const size_t num_samples{buffer.GetData().size() / SampleSizeFromFormat(format)};
return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
}
void Stream::PlayNextBuffer() {
if (!IsPlaying()) {
// Ensure we are in playing state before playing the next buffer
return;
}
if (active_buffer) {
// Do not queue a new buffer if we are already playing a buffer
return;
}
if (queued_buffers.empty()) {
// No queued buffers - we are effectively paused
return;
}
active_buffer = queued_buffers.front();
queued_buffers.pop();
CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
}
void Stream::ReleaseActiveBuffer() {
released_buffers.push(std::move(active_buffer));
release_callback();
PlayNextBuffer();
}
bool Stream::QueueBuffer(BufferPtr&& buffer) {
if (queued_buffers.size() < MaxAudioBufferCount) {
queued_buffers.push(std::move(buffer));
PlayNextBuffer();
return true;
}
return false;
}
bool Stream::ContainsBuffer(Buffer::Tag tag) const {
ASSERT_MSG(false, "Unimplemented");
return {};
}
std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(size_t max_count) {
std::vector<Buffer::Tag> tags;
for (size_t count = 0; count < max_count && !released_buffers.empty(); ++count) {
tags.push_back(released_buffers.front()->GetTag());
released_buffers.pop();
}
return tags;
}
} // namespace AudioCore

89
src/audio_core/stream.h Normal file
View File

@@ -0,0 +1,89 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include <queue>
#include "audio_core/buffer.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "core/core_timing.h"
namespace AudioCore {
using BufferPtr = std::shared_ptr<Buffer>;
/**
* Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut
*/
class Stream {
public:
/// Audio format of the stream
enum class Format {
Mono16,
Stereo16,
Multi51Channel16,
};
/// Callback function type, used to change guest state on a buffer being released
using ReleaseCallback = std::function<void()>;
Stream(int sample_rate, Format format, ReleaseCallback&& release_callback);
/// Plays the audio stream
void Play();
/// Stops the audio stream
void Stop();
/// Queues a buffer into the audio stream, returns true on success
bool QueueBuffer(BufferPtr&& buffer);
/// Returns true if the audio stream contains a buffer with the specified tag
bool ContainsBuffer(Buffer::Tag tag) const;
/// Returns a vector of recently released buffers specified by tag
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(size_t max_count);
/// Returns true if the stream is currently playing
bool IsPlaying() const {
return state == State::Playing;
}
/// Returns the number of queued buffers
size_t GetQueueSize() const {
return queued_buffers.size();
}
private:
/// Current state of the stream
enum class State {
Stopped,
Playing,
};
/// Plays the next queued buffer in the audio stream, starting playback if necessary
void PlayNextBuffer();
/// Releases the actively playing buffer, signalling that it has been completed
void ReleaseActiveBuffer();
/// Gets the number of core cycles when the specified buffer will be released
s64 GetBufferReleaseCycles(const Buffer& buffer) const;
int sample_rate; ///< Sample rate of the stream
Format format; ///< Format of the stream
ReleaseCallback release_callback; ///< Buffer release callback for the stream
State state{State::Stopped}; ///< Playback state of the stream
CoreTiming::EventType* release_event{}; ///< Core timing release event for the stream
BufferPtr active_buffer; ///< Actively playing buffer in the stream
std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream
std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream
};
} // namespace AudioCore

View File

@@ -173,9 +173,11 @@ void FileBackend::Write(const Entry& entry) {
SUB(Service, Friend) \
SUB(Service, FS) \
SUB(Service, HID) \
SUB(Service, LBL) \
SUB(Service, LDN) \
SUB(Service, LM) \
SUB(Service, MM) \
SUB(Service, NFC) \
SUB(Service, NFP) \
SUB(Service, NIFM) \
SUB(Service, NS) \

View File

@@ -60,9 +60,11 @@ enum class Class : ClassType {
Service_Friend, ///< The friend service
Service_FS, ///< The FS (Filesystem) service
Service_HID, ///< The HID (Human interface device) service
Service_LBL, ///< The LBL (LCD backlight) service
Service_LDN, ///< The LDN (Local domain network) service
Service_LM, ///< The LM (Logger) service
Service_MM, ///< The MM (Multimedia) service
Service_NFC, ///< The NFC (Near-field communication) service
Service_NFP, ///< The NFP service
Service_NIFM, ///< The NIFM (Network interface) service
Service_NS, ///< The NS services

View File

@@ -23,6 +23,8 @@ add_library(core STATIC
file_sys/partition_filesystem.h
file_sys/program_metadata.cpp
file_sys/program_metadata.h
file_sys/romfs.cpp
file_sys/romfs.h
file_sys/romfs_factory.cpp
file_sys/romfs_factory.h
file_sys/savedata_factory.cpp
@@ -35,6 +37,8 @@ add_library(core STATIC
file_sys/vfs_offset.h
file_sys/vfs_real.cpp
file_sys/vfs_real.h
file_sys/vfs_vector.cpp
file_sys/vfs_vector.h
frontend/emu_window.cpp
frontend/emu_window.h
frontend/framebuffer_layout.cpp
@@ -136,6 +140,8 @@ add_library(core STATIC
hle/service/bcat/bcat.h
hle/service/bcat/module.cpp
hle/service/bcat/module.h
hle/service/btdrv/btdrv.cpp
hle/service/btdrv/btdrv.h
hle/service/erpt/erpt.cpp
hle/service/erpt/erpt.h
hle/service/es/es.cpp
@@ -164,6 +170,8 @@ add_library(core STATIC
hle/service/hid/irs.h
hle/service/hid/xcd.cpp
hle/service/hid/xcd.h
hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h
hle/service/ldn/ldn.cpp
hle/service/ldn/ldn.h
hle/service/ldr/ldr.cpp
@@ -172,6 +180,8 @@ add_library(core STATIC
hle/service/lm/lm.h
hle/service/mm/mm_u.cpp
hle/service/mm/mm_u.h
hle/service/nfc/nfc.cpp
hle/service/nfc/nfc.h
hle/service/nfp/nfp.cpp
hle/service/nfp/nfp.h
hle/service/nfp/nfp_user.cpp
@@ -299,8 +309,8 @@ add_library(core STATIC
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static unicorn)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static opus unicorn)
if (ARCHITECTURE_x86_64)
target_sources(core PRIVATE

View File

@@ -177,6 +177,7 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
}
gpu_core = std::make_unique<Tegra::GPU>();
audio_core = std::make_unique<AudioCore::AudioOut>();
telemetry_session = std::make_unique<Core::TelemetrySession>();
service_manager = std::make_shared<Service::SM::ServiceManager>();
@@ -228,6 +229,7 @@ void System::Shutdown() {
service_manager.reset();
telemetry_session.reset();
gpu_core.reset();
audio_core.reset();
// Close all CPU/threading state
cpu_barrier->NotifyEnd();

View File

@@ -8,6 +8,7 @@
#include <memory>
#include <string>
#include <thread>
#include "audio_core/audio_out.h"
#include "common/common_types.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core_cpu.h"
@@ -131,6 +132,11 @@ public:
return *gpu_core;
}
/// Gets the AudioCore interface
AudioCore::AudioOut& AudioCore() {
return *audio_core;
}
/// Gets the scheduler for the CPU core that is currently running
Kernel::Scheduler& CurrentScheduler() {
return *CurrentCpuCore().Scheduler();
@@ -195,6 +201,7 @@ private:
/// AppLoader used to load the current executing application
std::unique_ptr<Loader::AppLoader> app_loader;
std::unique_ptr<Tegra::GPU> gpu_core;
std::unique_ptr<AudioCore::AudioOut> audio_core;
std::shared_ptr<Tegra::DebugContext> debug_context;
Kernel::SharedPtr<Kernel::Process> current_process;
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;

View File

@@ -9,6 +9,7 @@
#include "core/file_sys/content_archive.h"
#include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.h"
#include "romfs.h"
namespace FileSys {
@@ -46,21 +47,9 @@ struct PFS0Superblock {
};
static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
struct IVFCLevel {
u64_le offset;
u64_le size;
u32_le block_size;
u32_le reserved;
};
static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
struct RomFSSuperblock {
NCASectionHeaderBlock header_block;
u32_le magic;
u32_le magic_number;
INSERT_PADDING_BYTES(8);
std::array<IVFCLevel, 6> levels;
INSERT_PADDING_BYTES(64);
IVFCHeader ivfc;
};
static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size.");
@@ -92,8 +81,8 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
const size_t romfs_offset =
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
sb.levels[IVFC_MAX_LEVEL - 1].offset;
const size_t romfs_size = sb.levels[IVFC_MAX_LEVEL - 1].size;
sb.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
const size_t romfs_size = sb.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset));
romfs = files.back();
} else if (block.filesystem_type == NCASectionFilesystemType::PFS0) {

124
src/core/file_sys/romfs.cpp Normal file
View File

@@ -0,0 +1,124 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_offset.h"
#include "core/file_sys/vfs_vector.h"
namespace FileSys {
constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF;
struct TableLocation {
u64_le offset;
u64_le size;
};
static_assert(sizeof(TableLocation) == 0x10, "TableLocation has incorrect size.");
struct RomFSHeader {
u64_le header_size;
TableLocation directory_hash;
TableLocation directory_meta;
TableLocation file_hash;
TableLocation file_meta;
u64_le data_offset;
};
static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size.");
struct DirectoryEntry {
u32_le sibling;
u32_le child_dir;
u32_le child_file;
u32_le hash;
u32_le name_length;
};
static_assert(sizeof(DirectoryEntry) == 0x14, "DirectoryEntry has incorrect size.");
struct FileEntry {
u32_le parent;
u32_le sibling;
u64_le offset;
u64_le size;
u32_le hash;
u32_le name_length;
};
static_assert(sizeof(FileEntry) == 0x20, "FileEntry has incorrect size.");
template <typename Entry>
static std::pair<Entry, std::string> GetEntry(const VirtualFile& file, size_t offset) {
Entry entry{};
if (file->ReadObject(&entry, offset) != sizeof(Entry))
return {};
std::string string(entry.name_length, '\0');
if (file->ReadArray(&string[0], string.size(), offset + sizeof(Entry)) != string.size())
return {};
return {entry, string};
}
void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 this_file_offset,
std::shared_ptr<VectorVfsDirectory> parent) {
while (true) {
auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset);
parent->AddFile(std::make_shared<OffsetVfsFile>(
file, entry.first.size, entry.first.offset + data_offset, entry.second, parent));
if (entry.first.sibling == ROMFS_ENTRY_EMPTY)
break;
this_file_offset = entry.first.sibling;
}
}
void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, size_t data_offset,
u32 this_dir_offset, std::shared_ptr<VectorVfsDirectory> parent) {
while (true) {
auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset);
auto current = std::make_shared<VectorVfsDirectory>(
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parent, entry.second);
if (entry.first.child_file != ROMFS_ENTRY_EMPTY) {
ProcessFile(file, file_offset, data_offset, entry.first.child_file, current);
}
if (entry.first.child_dir != ROMFS_ENTRY_EMPTY) {
ProcessDirectory(file, dir_offset, file_offset, data_offset, entry.first.child_dir,
current);
}
parent->AddDirectory(current);
if (entry.first.sibling == ROMFS_ENTRY_EMPTY)
break;
this_dir_offset = entry.first.sibling;
}
}
VirtualDir ExtractRomFS(VirtualFile file) {
RomFSHeader header{};
if (file->ReadObject(&header) != sizeof(RomFSHeader))
return nullptr;
if (header.header_size != sizeof(RomFSHeader))
return nullptr;
const u64 file_offset = header.file_meta.offset;
const u64 dir_offset = header.directory_meta.offset + 4;
const auto root =
std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{},
file->GetContainingDirectory(), file->GetName());
ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root);
VirtualDir out = std::move(root);
while (out->GetSubdirectory("") != nullptr)
out = out->GetSubdirectory("");
return out;
}
} // namespace FileSys

35
src/core/file_sys/romfs.h Normal file
View File

@@ -0,0 +1,35 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "common/common_funcs.h"
#include "common/swap.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
struct IVFCLevel {
u64_le offset;
u64_le size;
u32_le block_size;
u32_le reserved;
};
static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
struct IVFCHeader {
u32_le magic;
u32_le magic_number;
INSERT_PADDING_BYTES(8);
std::array<IVFCLevel, 6> levels;
INSERT_PADDING_BYTES(64);
};
static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
// Converts a RomFS binary blob to VFS Filesystem
// Returns nullptr on failure
VirtualDir ExtractRomFS(VirtualFile file);
} // namespace FileSys

View File

@@ -46,6 +46,13 @@ size_t VfsFile::WriteBytes(const std::vector<u8>& data, size_t offset) {
return Write(data.data(), data.size(), offset);
}
std::string VfsFile::GetFullPath() const {
if (GetContainingDirectory() == nullptr)
return "/" + GetName();
return GetContainingDirectory()->GetFullPath() + "/" + GetName();
}
std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) const {
auto vec = FileUtil::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
@@ -243,6 +250,13 @@ bool VfsDirectory::Copy(std::string_view src, std::string_view dest) {
return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
}
std::string VfsDirectory::GetFullPath() const {
if (IsRoot())
return GetName();
return GetParentDirectory()->GetFullPath() + "/" + GetName();
}
bool ReadOnlyVfsDirectory::IsWritable() const {
return false;
}
@@ -270,4 +284,13 @@ bool ReadOnlyVfsDirectory::DeleteFile(std::string_view name) {
bool ReadOnlyVfsDirectory::Rename(std::string_view name) {
return false;
}
bool VfsRawCopy(VirtualFile src, VirtualFile dest) {
if (src == nullptr || dest == nullptr)
return false;
if (!dest->Resize(src->GetSize()))
return false;
std::vector<u8> data = src->ReadAllBytes();
return dest->WriteBytes(data, 0) == data.size();
}
} // namespace FileSys

View File

@@ -113,6 +113,9 @@ struct VfsFile : NonCopyable {
// Renames the file to name. Returns whether or not the operation was successsful.
virtual bool Rename(std::string_view name) = 0;
// Returns the full path of this file as a string, recursively
virtual std::string GetFullPath() const;
};
// A class representing a directory in an abstract filesystem.
@@ -213,6 +216,17 @@ struct VfsDirectory : NonCopyable {
return ReplaceFileWithSubdirectory(file_p, std::make_shared<Directory>(file_p));
}
bool InterpretAsDirectory(const std::function<VirtualDir(VirtualFile)>& function,
const std::string& file) {
auto file_p = GetFile(file);
if (file_p == nullptr)
return false;
return ReplaceFileWithSubdirectory(file_p, function(file_p));
}
// Returns the full path of this directory as a string, recursively
virtual std::string GetFullPath() const;
protected:
// Backend for InterpretAsDirectory.
// Removes all references to file and adds a reference to dir in the directory's implementation.
@@ -230,4 +244,10 @@ struct ReadOnlyVfsDirectory : public VfsDirectory {
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
};
// A method that copies the raw data between two different implementations of VirtualFile. If you
// are using the same implementation, it is probably better to use the Copy method in the parent
// directory of src/dest.
bool VfsRawCopy(VirtualFile src, VirtualFile dest);
} // namespace FileSys

View File

@@ -10,8 +10,9 @@
namespace FileSys {
OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, size_t size_, size_t offset_,
std::string name_)
: file(std::move(file_)), offset(offset_), size(size_), name(std::move(name_)) {}
std::string name_, VirtualDir parent_)
: file(file_), offset(offset_), size(size_), name(std::move(name_)),
parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {}
std::string OffsetVfsFile::GetName() const {
return name.empty() ? file->GetName() : name;
@@ -35,7 +36,7 @@ bool OffsetVfsFile::Resize(size_t new_size) {
}
std::shared_ptr<VfsDirectory> OffsetVfsFile::GetContainingDirectory() const {
return file->GetContainingDirectory();
return parent;
}
bool OffsetVfsFile::IsWritable() const {

View File

@@ -17,7 +17,7 @@ namespace FileSys {
// the size of this wrapper.
struct OffsetVfsFile : public VfsFile {
OffsetVfsFile(std::shared_ptr<VfsFile> file, size_t size, size_t offset = 0,
std::string new_name = "");
std::string new_name = "", VirtualDir new_parent = nullptr);
std::string GetName() const override;
size_t GetSize() const override;
@@ -44,6 +44,7 @@ private:
size_t offset;
size_t size;
std::string name;
VirtualDir parent;
};
} // namespace FileSys

View File

@@ -195,6 +195,12 @@ bool RealVfsDirectory::Rename(std::string_view name) {
return FileUtil::Rename(path, new_name);
}
std::string RealVfsDirectory::GetFullPath() const {
auto out = path;
std::replace(out.begin(), out.end(), '\\', '/');
return out;
}
bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
const auto iter = std::find(files.begin(), files.end(), file);
if (iter == files.end())

View File

@@ -41,7 +41,7 @@ private:
// An implementation of VfsDirectory that represents a directory on the user's computer.
struct RealVfsDirectory : public VfsDirectory {
RealVfsDirectory(const std::string& path, Mode perms);
RealVfsDirectory(const std::string& path, Mode perms = Mode::Read);
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
@@ -54,6 +54,7 @@ struct RealVfsDirectory : public VfsDirectory {
bool DeleteSubdirectory(std::string_view name) override;
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
std::string GetFullPath() const override;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;

View File

@@ -0,0 +1,83 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include "core/file_sys/vfs_vector.h"
namespace FileSys {
VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
std::vector<VirtualDir> dirs_, VirtualDir parent_,
std::string name_)
: files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)),
name(std::move(name_)) {}
std::vector<std::shared_ptr<VfsFile>> VectorVfsDirectory::GetFiles() const {
return files;
}
std::vector<std::shared_ptr<VfsDirectory>> VectorVfsDirectory::GetSubdirectories() const {
return dirs;
}
bool VectorVfsDirectory::IsWritable() const {
return false;
}
bool VectorVfsDirectory::IsReadable() const {
return true;
}
std::string VectorVfsDirectory::GetName() const {
return name;
}
std::shared_ptr<VfsDirectory> VectorVfsDirectory::GetParentDirectory() const {
return parent;
}
template <typename T>
static bool FindAndRemoveVectorElement(std::vector<T>& vec, std::string_view name) {
auto iter = std::find_if(vec.begin(), vec.end(), [name](T e) { return e->GetName() == name; });
if (iter == vec.end())
return false;
auto old_size = vec.size();
vec.erase(iter);
return true;
}
bool VectorVfsDirectory::DeleteSubdirectory(std::string_view name) {
return FindAndRemoveVectorElement(dirs, name);
}
bool VectorVfsDirectory::DeleteFile(std::string_view name) {
return FindAndRemoveVectorElement(files, name);
}
bool VectorVfsDirectory::Rename(std::string_view name_) {
name = name_;
return true;
}
std::shared_ptr<VfsDirectory> VectorVfsDirectory::CreateSubdirectory(std::string_view name) {
return nullptr;
}
std::shared_ptr<VfsFile> VectorVfsDirectory::CreateFile(std::string_view name) {
return nullptr;
}
void VectorVfsDirectory::AddFile(VirtualFile file) {
files.push_back(std::move(file));
}
void VectorVfsDirectory::AddDirectory(VirtualDir dir) {
dirs.push_back(std::move(dir));
}
bool VectorVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
if (!DeleteFile(file->GetName()))
return false;
dirs.emplace_back(dir);
return true;
}
} // namespace FileSys

View File

@@ -0,0 +1,44 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/file_sys/vfs.h"
namespace FileSys {
// An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
// Vector data is supplied upon construction.
struct VectorVfsDirectory : public VfsDirectory {
explicit VectorVfsDirectory(std::vector<VirtualFile> files = {},
std::vector<VirtualDir> dirs = {}, VirtualDir parent = nullptr,
std::string name = "");
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::string GetName() const override;
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
bool DeleteSubdirectory(std::string_view name) override;
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
virtual void AddFile(VirtualFile file);
virtual void AddDirectory(VirtualDir dir);
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
std::vector<VirtualFile> files;
std::vector<VirtualDir> dirs;
VirtualDir parent;
std::string name;
};
} // namespace FileSys

View File

@@ -5,8 +5,7 @@
#include <array>
#include <vector>
#include "common/logging/log.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/hle_ipc.h"
@@ -14,17 +13,22 @@
namespace Service::Audio {
/// Switch sample rate frequency
constexpr u32 sample_rate{48000};
/// TODO(st4rk): dynamic number of channels, as I think Switch has support
/// to more audio channels (probably when Docked I guess)
constexpr u32 audio_channels{2};
/// TODO(st4rk): find a proper value for the audio_ticks
constexpr u64 audio_ticks{static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / 500)};
namespace ErrCodes {
enum {
ErrorUnknown = 2,
BufferCountExceeded = 8,
};
}
constexpr std::array<char, 10> DefaultDevice{{"DeviceOut"}};
constexpr int DefaultSampleRate{48000};
class IAudioOut final : public ServiceFramework<IAudioOut> {
public:
IAudioOut() : ServiceFramework("IAudioOut"), audio_out_state(AudioState::Stopped) {
IAudioOut(AudoutParams audio_params)
: ServiceFramework("IAudioOut"), audio_params(audio_params),
audio_core(Core::System::GetInstance().AudioCore()) {
static const FunctionInfo functions[] = {
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
{1, &IAudioOut::StartAudioOut, "StartAudioOut"},
@@ -32,66 +36,65 @@ public:
{3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"},
{4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
{5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffer"},
{6, nullptr, "ContainsAudioOutBuffer"},
{6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"},
{7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"},
{8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"},
{9, nullptr, "GetAudioOutBufferCount"},
{9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"},
{10, nullptr, "GetAudioOutPlayedSampleCount"},
{11, nullptr, "FlushAudioOutBuffers"},
};
RegisterHandlers(functions);
// This is the event handle used to check if the audio buffer was released
buffer_event =
Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent");
buffer_event = Kernel::Event::Create(Kernel::ResetType::Sticky, "IAudioOutBufferReleased");
// Register event callback to update the Audio Buffer
audio_event = CoreTiming::RegisterEvent(
"IAudioOut::UpdateAudioBuffersCallback", [this](u64 userdata, int cycles_late) {
UpdateAudioBuffersCallback();
CoreTiming::ScheduleEvent(audio_ticks - cycles_late, audio_event);
});
// Start the audio event
CoreTiming::ScheduleEvent(audio_ticks, audio_event);
}
~IAudioOut() {
CoreTiming::UnscheduleEvent(audio_event, 0);
stream = audio_core.OpenStream(audio_params.sample_rate, audio_params.channel_count,
[=]() { buffer_event->Signal(); });
}
private:
struct AudioBuffer {
u64_le next;
u64_le buffer;
u64_le buffer_capacity;
u64_le buffer_size;
u64_le offset;
};
static_assert(sizeof(AudioBuffer) == 0x28, "AudioBuffer is an invalid size");
void GetAudioOutState(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u32>(audio_out_state));
rb.Push(static_cast<u32>(stream->IsPlaying() ? AudioState::Started : AudioState::Stopped));
}
void StartAudioOut(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
LOG_DEBUG(Service_Audio, "called");
// Start audio
audio_out_state = AudioState::Started;
if (stream->IsPlaying()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(ErrorModule::Audio, ErrCodes::ErrorUnknown));
return;
}
audio_core.StartStream(stream);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void StopAudioOut(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
LOG_DEBUG(Service_Audio, "called");
// Stop audio
audio_out_state = AudioState::Stopped;
queue_keys.clear();
audio_core.StopStream(stream);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
@@ -99,101 +102,107 @@ private:
}
void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
LOG_DEBUG(Service_Audio, "(STUBBED) called {}", ctx.Description());
IPC::RequestParser rp{ctx};
const u64 key{rp.Pop<u64>()};
queue_keys.insert(queue_keys.begin(), key);
const auto& input_buffer{ctx.ReadBuffer()};
ASSERT_MSG(input_buffer.size() == sizeof(AudioBuffer),
"AudioBuffer input is an invalid size!");
AudioBuffer audio_buffer{};
std::memcpy(&audio_buffer, input_buffer.data(), sizeof(AudioBuffer));
const u64 tag{rp.Pop<u64>()};
std::vector<u8> data(audio_buffer.buffer_size);
Memory::ReadBlock(audio_buffer.buffer, data.data(), data.size());
if (!audio_core.QueueBuffer(stream, tag, std::move(data))) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(ErrorModule::Audio, ErrCodes::BufferCountExceeded));
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
LOG_DEBUG(Service_Audio, "called {}", ctx.Description());
IPC::RequestParser rp{ctx};
const u64 max_count{ctx.GetWriteBufferSize() / sizeof(u64)};
const auto released_buffers{audio_core.GetTagsAndReleaseBuffers(stream, max_count)};
// TODO(st4rk): This is how libtransistor currently implements the
// GetReleasedAudioOutBuffer, it should return the key (a VAddr) to the app and this address
// is used to know which buffer should be filled with data and send again to the service
// through AppendAudioOutBuffer. Check if this is the proper way to do it.
u64 key{0};
if (queue_keys.size()) {
key = queue_keys.back();
queue_keys.pop_back();
}
ctx.WriteBuffer(&key, sizeof(u64));
std::vector<u64> tags{released_buffers};
tags.resize(max_count);
ctx.WriteBuffer(tags);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
// TODO(st4rk): This might be the total of released buffers, needs to be verified on
// hardware
rb.Push<u32>(static_cast<u32>(queue_keys.size()));
rb.Push<u32>(static_cast<u32>(released_buffers.size()));
}
void UpdateAudioBuffersCallback() {
if (audio_out_state != AudioState::Started) {
return;
}
if (queue_keys.empty()) {
return;
}
buffer_event->Signal();
void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::RequestParser rp{ctx};
const u64 tag{rp.Pop<u64>()};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(stream->ContainsBuffer(tag));
}
enum class AudioState : u32 {
Started,
Stopped,
};
void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u32>(stream->GetQueueSize()));
}
/// This is used to trigger the audio event callback that is going to read the samples from the
/// audio_buffer list and enqueue the samples using the sink (audio_core).
CoreTiming::EventType* audio_event;
AudioCore::AudioOut& audio_core;
AudioCore::StreamPtr stream;
AudoutParams audio_params{};
/// This is the evend handle used to check if the audio buffer was released
Kernel::SharedPtr<Kernel::Event> buffer_event;
/// (st4rk): This is just a temporary workaround for the future implementation. Libtransistor
/// uses the key as an address in the App, so we need to return when the
/// GetReleasedAudioOutBuffer_1 is called, otherwise we'll run in problems, because
/// libtransistor uses the key returned as an pointer.
std::vector<u64> queue_keys;
AudioState audio_out_state;
};
void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
LOG_DEBUG(Service_Audio, "called");
IPC::RequestParser rp{ctx};
constexpr std::array<char, 15> audio_interface{{"AudioInterface"}};
ctx.WriteBuffer(audio_interface);
ctx.WriteBuffer(DefaultDevice);
IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0);
rb.Push(RESULT_SUCCESS);
// TODO(st4rk): We're currently returning only one audio interface (stringlist size). However,
// it's highly possible to have more than one interface (despite that libtransistor requires
// only one).
rb.Push<u32>(1);
rb.Push<u32>(1); // Amount of audio devices
}
void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
LOG_DEBUG(Service_Audio, "called");
if (!audio_out_interface) {
audio_out_interface = std::make_shared<IAudioOut>();
ctx.WriteBuffer(DefaultDevice);
IPC::RequestParser rp{ctx};
auto params{rp.PopRaw<AudoutParams>()};
if (params.channel_count <= 2) {
// Mono does not exist for audout
params.channel_count = 2;
} else {
params.channel_count = 6;
}
if (!params.sample_rate) {
params.sample_rate = DefaultSampleRate;
}
// TODO(bunnei): Support more than one IAudioOut interface. When we add this, ListAudioOutsImpl
// will likely need to be updated as well.
ASSERT_MSG(!audio_out_interface, "Unimplemented");
audio_out_interface = std::make_shared<IAudioOut>(std::move(params));
IPC::ResponseBuilder rb{ctx, 6, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(sample_rate);
rb.Push<u32>(audio_channels);
rb.Push<u32>(DefaultSampleRate);
rb.Push<u32>(params.channel_count);
rb.Push<u32>(static_cast<u32>(PcmFormat::Int16));
rb.Push<u32>(0); // This field is unknown
rb.Push<u32>(static_cast<u32>(AudioState::Stopped));
rb.PushIpcInterface<Audio::IAudioOut>(audio_out_interface);
}

View File

@@ -12,6 +12,18 @@ class HLERequestContext;
namespace Service::Audio {
struct AudoutParams {
s32_le sample_rate;
u16_le channel_count;
INSERT_PADDING_BYTES(2);
};
static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size");
enum class AudioState : u32 {
Started,
Stopped,
};
class IAudioOut;
class AudOutU final : public ServiceFramework<AudOutU> {

View File

@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include <opus.h>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
@@ -9,19 +11,142 @@
namespace Service::Audio {
struct OpusDeleter {
void operator()(void* ptr) const {
operator delete(ptr);
}
};
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
public:
IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoder, OpusDeleter> decoder, u32 sample_rate,
u32 channel_count)
: ServiceFramework("IHardwareOpusDecoderManager"), decoder(std::move(decoder)),
sample_rate(sample_rate), channel_count(channel_count) {
static const FunctionInfo functions[] = {
{0, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
{1, nullptr, "SetContext"},
{2, nullptr, "DecodeInterleavedForMultiStream"},
{3, nullptr, "SetContextForMultiStream"},
{4, nullptr, "Unknown4"},
{5, nullptr, "Unknown5"},
{6, nullptr, "Unknown6"},
{7, nullptr, "Unknown7"},
};
RegisterHandlers(functions);
}
private:
void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
u32 consumed = 0;
u32 sample_count = 0;
std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16));
if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples)) {
IPC::ResponseBuilder rb{ctx, 2};
// TODO(ogniK): Use correct error code
rb.Push(ResultCode(-1));
return;
}
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(consumed);
rb.Push<u32>(sample_count);
ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
}
bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input,
std::vector<opus_int16>& output) {
size_t raw_output_sz = output.size() * sizeof(opus_int16);
if (sizeof(OpusHeader) > input.size())
return false;
OpusHeader hdr{};
std::memcpy(&hdr, input.data(), sizeof(OpusHeader));
if (sizeof(OpusHeader) + static_cast<u32>(hdr.sz) > input.size()) {
return false;
}
auto frame = input.data() + sizeof(OpusHeader);
auto decoded_sample_count = opus_packet_get_nb_samples(
frame, static_cast<opus_int32>(input.size() - sizeof(OpusHeader)),
static_cast<opus_int32>(sample_rate));
if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz)
return false;
auto out_sample_count =
opus_decode(decoder.get(), frame, hdr.sz, output.data(),
(static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)), 0);
if (out_sample_count < 0)
return false;
sample_count = out_sample_count;
consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz);
return true;
}
struct OpusHeader {
u32_be sz; // Needs to be BE for some odd reason
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(OpusHeader) == 0x8, "OpusHeader is an invalid size");
std::unique_ptr<OpusDecoder, OpusDeleter> decoder;
u32 sample_rate;
u32 channel_count;
};
static size_t WorkerBufferSize(u32 channel_count) {
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
return opus_decoder_get_size(static_cast<int>(channel_count));
}
void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::RequestParser rp{ctx};
auto sample_rate = rp.Pop<u32>();
auto channel_count = rp.Pop<u32>();
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
sample_rate == 12000 || sample_rate == 8000,
"Invalid sample rate");
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count));
LOG_DEBUG(Audio, "called worker_buffer_sz={}", worker_buffer_sz);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0x4000);
rb.Push<u32>(worker_buffer_sz);
}
void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto sample_rate = rp.Pop<u32>();
auto channel_count = rp.Pop<u32>();
auto buffer_sz = rp.Pop<u32>();
LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate,
channel_count, buffer_sz);
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
sample_rate == 12000 || sample_rate == 8000,
"Invalid sample rate");
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
size_t worker_sz = WorkerBufferSize(channel_count);
ASSERT_MSG(buffer_sz < worker_sz, "Worker buffer too large");
std::unique_ptr<OpusDecoder, OpusDeleter> decoder{
static_cast<OpusDecoder*>(operator new(worker_sz))};
if (opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
IPC::ResponseBuilder rb{ctx, 2};
// TODO(ogniK): Use correct error code
rb.Push(ResultCode(-1));
return;
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IHardwareOpusDecoderManager>(std::move(decoder), sample_rate,
channel_count);
}
HwOpus::HwOpus() : ServiceFramework("hwopus") {
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
{0, &HwOpus::OpenOpusDecoder, "OpenOpusDecoder"},
{1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},
{2, nullptr, "InitializeMultiStream"},
{3, nullptr, "GetWorkBufferSizeMultiStream"},
{2, nullptr, "OpenOpusDecoderForMultiStream"},
{3, nullptr, "GetWorkBufferSizeForMultiStream"},
};
RegisterHandlers(functions);
}

View File

@@ -14,6 +14,7 @@ public:
~HwOpus() = default;
private:
void OpenOpusDecoder(Kernel::HLERequestContext& ctx);
void GetWorkBufferSize(Kernel::HLERequestContext& ctx);
};

View File

@@ -0,0 +1,72 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/btdrv/btdrv.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
namespace Service::BtDrv {
class BtDrv final : public ServiceFramework<BtDrv> {
public:
explicit BtDrv() : ServiceFramework{"btdrv"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Unknown"},
{1, nullptr, "Init"},
{2, nullptr, "Enable"},
{3, nullptr, "Disable"},
{4, nullptr, "CleanupAndShutdown"},
{5, nullptr, "GetAdapterProperties"},
{6, nullptr, "GetAdapterProperty"},
{7, nullptr, "SetAdapterProperty"},
{8, nullptr, "StartDiscovery"},
{9, nullptr, "CancelDiscovery"},
{10, nullptr, "CreateBond"},
{11, nullptr, "RemoveBond"},
{12, nullptr, "CancelBond"},
{13, nullptr, "PinReply"},
{14, nullptr, "SspReply"},
{15, nullptr, "Unknown2"},
{16, nullptr, "InitInterfaces"},
{17, nullptr, "HidHostInterface_Connect"},
{18, nullptr, "HidHostInterface_Disconnect"},
{19, nullptr, "HidHostInterface_SendData"},
{20, nullptr, "HidHostInterface_SendData2"},
{21, nullptr, "HidHostInterface_SetReport"},
{22, nullptr, "HidHostInterface_GetReport"},
{23, nullptr, "HidHostInterface_WakeController"},
{24, nullptr, "HidHostInterface_AddPairedDevice"},
{25, nullptr, "HidHostInterface_GetPairedDevice"},
{26, nullptr, "HidHostInterface_CleanupAndShutdown"},
{27, nullptr, "Unknown3"},
{28, nullptr, "ExtInterface_SetTSI"},
{29, nullptr, "ExtInterface_SetBurstMode"},
{30, nullptr, "ExtInterface_SetZeroRetran"},
{31, nullptr, "ExtInterface_SetMcMode"},
{32, nullptr, "ExtInterface_StartLlrMode"},
{33, nullptr, "ExtInterface_ExitLlrMode"},
{34, nullptr, "ExtInterface_SetRadio"},
{35, nullptr, "ExtInterface_SetVisibility"},
{36, nullptr, "Unknown4"},
{37, nullptr, "Unknown5"},
{38, nullptr, "HidHostInterface_GetLatestPlr"},
{39, nullptr, "ExtInterface_GetPendingConnections"},
{40, nullptr, "HidHostInterface_GetChannelMap"},
{41, nullptr, "SetIsBluetoothBoostEnabled"},
{42, nullptr, "GetIsBluetoothBoostEnabled"},
{43, nullptr, "SetIsBluetoothAfhEnabled"},
{44, nullptr, "GetIsBluetoothAfhEnabled"},
};
// clang-format on
RegisterHandlers(functions);
}
};
void InstallInterfaces(SM::ServiceManager& sm) {
std::make_shared<BtDrv>()->InstallAsService(sm);
}
} // namespace Service::BtDrv

View File

@@ -0,0 +1,16 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Service::SM {
class ServiceManager;
}
namespace Service::BtDrv {
/// Registers all BtDrv services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& sm);
} // namespace Service::BtDrv

View File

@@ -0,0 +1,90 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/lbl/lbl.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
namespace Service::LBL {
class LBL final : public ServiceFramework<LBL> {
public:
explicit LBL() : ServiceFramework{"lbl"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Unknown1"},
{1, nullptr, "Unknown2"},
{2, nullptr, "Unknown3"},
{3, nullptr, "Unknown4"},
{4, nullptr, "Unknown5"},
{5, nullptr, "Unknown6"},
{6, nullptr, "TurnOffBacklight"},
{7, nullptr, "TurnOnBacklight"},
{8, nullptr, "GetBacklightStatus"},
{9, nullptr, "Unknown7"},
{10, nullptr, "Unknown8"},
{11, nullptr, "Unknown9"},
{12, nullptr, "Unknown10"},
{13, nullptr, "Unknown11"},
{14, nullptr, "Unknown12"},
{15, nullptr, "Unknown13"},
{16, nullptr, "ReadRawLightSensor"},
{17, nullptr, "Unknown14"},
{18, nullptr, "Unknown15"},
{19, nullptr, "Unknown16"},
{20, nullptr, "Unknown17"},
{21, nullptr, "Unknown18"},
{22, nullptr, "Unknown19"},
{23, nullptr, "Unknown20"},
{24, nullptr, "Unknown21"},
{25, nullptr, "Unknown22"},
{26, &LBL::EnableVrMode, "EnableVrMode"},
{27, &LBL::DisableVrMode, "DisableVrMode"},
{28, &LBL::GetVrMode, "GetVrMode"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void EnableVrMode(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
vr_mode_enabled = true;
LOG_DEBUG(Service_LBL, "called");
}
void DisableVrMode(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
vr_mode_enabled = false;
LOG_DEBUG(Service_LBL, "called");
}
void GetVrMode(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(vr_mode_enabled);
LOG_DEBUG(Service_LBL, "called");
}
bool vr_mode_enabled = false;
};
void InstallInterfaces(SM::ServiceManager& sm) {
std::make_shared<LBL>()->InstallAsService(sm);
}
} // namespace Service::LBL

View File

@@ -0,0 +1,15 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Service::SM {
class ServiceManager;
}
namespace Service::LBL {
void InstallInterfaces(SM::ServiceManager& sm);
} // namespace Service::LBL

View File

@@ -0,0 +1,222 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
namespace Service::NFC {
class IAm final : public ServiceFramework<IAm> {
public:
explicit IAm() : ServiceFramework{"IAm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
{1, nullptr, "Finalize"},
{2, nullptr, "NotifyForegroundApplet"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class NFC_AM final : public ServiceFramework<NFC_AM> {
public:
explicit NFC_AM() : ServiceFramework{"nfc:am"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NFC_AM::CreateAmInterface, "CreateAmInterface"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void CreateAmInterface(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAm>();
LOG_DEBUG(Service_NFC, "called");
}
};
class MFIUser final : public ServiceFramework<MFIUser> {
public:
explicit MFIUser() : ServiceFramework{"IUser"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
{1, nullptr, "Finalize"},
{2, nullptr, "ListDevices"},
{3, nullptr, "StartDetection"},
{4, nullptr, "StopDetection"},
{5, nullptr, "Read"},
{6, nullptr, "Write"},
{7, nullptr, "GetTagInfo"},
{8, nullptr, "GetActivateEventHandle"},
{9, nullptr, "GetDeactivateEventHandle"},
{10, nullptr, "GetState"},
{11, nullptr, "GetDeviceState"},
{12, nullptr, "GetNpadId"},
{13, nullptr, "GetAvailabilityChangeEventHandle"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class NFC_MF_U final : public ServiceFramework<NFC_MF_U> {
public:
explicit NFC_MF_U() : ServiceFramework{"nfc:mf:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NFC_MF_U::CreateUserInterface, "CreateUserInterface"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void CreateUserInterface(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<MFIUser>();
LOG_DEBUG(Service_NFC, "called");
}
};
class IUser final : public ServiceFramework<IUser> {
public:
explicit IUser() : ServiceFramework{"IUser"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
{1, nullptr, "Finalize"},
{2, nullptr, "GetState"},
{3, nullptr, "IsNfcEnabled"},
{400, nullptr, "Initialize"},
{401, nullptr, "Finalize"},
{402, nullptr, "GetState"},
{403, nullptr, "IsNfcEnabled"},
{404, nullptr, "ListDevices"},
{405, nullptr, "GetDeviceState"},
{406, nullptr, "GetNpadId"},
{407, nullptr, "AttachAvailabilityChangeEvent"},
{408, nullptr, "StartDetection"},
{409, nullptr, "StopDetection"},
{410, nullptr, "GetTagInfo"},
{411, nullptr, "AttachActivateEvent"},
{412, nullptr, "AttachDeactivateEvent"},
{1000, nullptr, "ReadMifare"},
{1001, nullptr, "WriteMifare"},
{1300, nullptr, "SendCommandByPassThrough"},
{1301, nullptr, "KeepPassThroughSession"},
{1302, nullptr, "ReleasePassThroughSession"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class NFC_U final : public ServiceFramework<NFC_U> {
public:
explicit NFC_U() : ServiceFramework{"nfc:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NFC_U::CreateUserInterface, "CreateUserInterface"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void CreateUserInterface(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IUser>();
LOG_DEBUG(Service_NFC, "called");
}
};
class ISystem final : public ServiceFramework<ISystem> {
public:
explicit ISystem() : ServiceFramework{"ISystem"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
{1, nullptr, "Finalize"},
{2, nullptr, "GetState"},
{3, nullptr, "IsNfcEnabled"},
{100, nullptr, "SetNfcEnabled"},
{400, nullptr, "InitializeSystem"},
{401, nullptr, "FinalizeSystem"},
{402, nullptr, "GetState"},
{403, nullptr, "IsNfcEnabled"},
{404, nullptr, "ListDevices"},
{405, nullptr, "GetDeviceState"},
{406, nullptr, "GetNpadId"},
{407, nullptr, "AttachAvailabilityChangeEvent"},
{408, nullptr, "StartDetection"},
{409, nullptr, "StopDetection"},
{410, nullptr, "GetTagInfo"},
{411, nullptr, "AttachActivateEvent"},
{412, nullptr, "AttachDeactivateEvent"},
{500, nullptr, "SetNfcEnabled"},
{1000, nullptr, "ReadMifare"},
{1001, nullptr, "WriteMifare"},
{1300, nullptr, "SendCommandByPassThrough"},
{1301, nullptr, "KeepPassThroughSession"},
{1302, nullptr, "ReleasePassThroughSession"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class NFC_SYS final : public ServiceFramework<NFC_SYS> {
public:
explicit NFC_SYS() : ServiceFramework{"nfc:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NFC_SYS::CreateSystemInterface, "CreateSystemInterface"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void CreateSystemInterface(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISystem>();
LOG_DEBUG(Service_NFC, "called");
}
};
void InstallInterfaces(SM::ServiceManager& sm) {
std::make_shared<NFC_AM>()->InstallAsService(sm);
std::make_shared<NFC_MF_U>()->InstallAsService(sm);
std::make_shared<NFC_U>()->InstallAsService(sm);
std::make_shared<NFC_SYS>()->InstallAsService(sm);
}
} // namespace Service::NFC

View File

@@ -0,0 +1,15 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Service::SM {
class ServiceManager;
}
namespace Service::NFC {
void InstallInterfaces(SM::ServiceManager& sm);
} // namespace Service::NFC

View File

@@ -21,6 +21,7 @@
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/audio/audio.h"
#include "core/hle/service/bcat/bcat.h"
#include "core/hle/service/btdrv/btdrv.h"
#include "core/hle/service/erpt/erpt.h"
#include "core/hle/service/es/es.h"
#include "core/hle/service/eupld/eupld.h"
@@ -29,10 +30,12 @@
#include "core/hle/service/friend/friend.h"
#include "core/hle/service/grc/grc.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/lbl/lbl.h"
#include "core/hle/service/ldn/ldn.h"
#include "core/hle/service/ldr/ldr.h"
#include "core/hle/service/lm/lm.h"
#include "core/hle/service/mm/mm_u.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/nim/nim.h"
@@ -193,8 +196,9 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm) {
AM::InstallInterfaces(*sm, nv_flinger);
AOC::InstallInterfaces(*sm);
APM::InstallInterfaces(*sm);
BCAT::InstallInterfaces(*sm);
Audio::InstallInterfaces(*sm);
BCAT::InstallInterfaces(*sm);
BtDrv::InstallInterfaces(*sm);
ERPT::InstallInterfaces(*sm);
ES::InstallInterfaces(*sm);
EUPLD::InstallInterfaces(*sm);
@@ -203,10 +207,12 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm) {
Friend::InstallInterfaces(*sm);
GRC::InstallInterfaces(*sm);
HID::InstallInterfaces(*sm);
LBL::InstallInterfaces(*sm);
LDN::InstallInterfaces(*sm);
LDR::InstallInterfaces(*sm);
LM::InstallInterfaces(*sm);
MM::InstallInterfaces(*sm);
NFC::InstallInterfaces(*sm);
NFP::InstallInterfaces(*sm);
NIFM::InstallInterfaces(*sm);
NIM::InstallInterfaces(*sm);

View File

@@ -207,15 +207,27 @@ void GMainWindow::InitializeRecentFileMenuActions() {
void GMainWindow::InitializeHotkeys() {
RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
RegisterHotkey("Main Window", "Start Emulation");
RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4));
RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
Qt::ApplicationShortcut);
RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"),
Qt::ApplicationShortcut);
LoadHotkeys();
connect(GetHotkey("Main Window", "Load File", this), &QShortcut::activated, this,
&GMainWindow::OnMenuLoadFile);
connect(GetHotkey("Main Window", "Start Emulation", this), &QShortcut::activated, this,
&GMainWindow::OnStartGame);
connect(GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated, this, [&] {
if (emulation_running) {
if (emu_thread->IsRunning()) {
OnPauseGame();
} else {
OnStartGame();
}
}
});
connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activated,
ui.action_Fullscreen, &QAction::trigger);
connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activatedAmbiguously,
@@ -226,6 +238,10 @@ void GMainWindow::InitializeHotkeys() {
ToggleFullscreen();
}
});
connect(GetHotkey("Main Window", "Toggle Speed Limit", this), &QShortcut::activated, this, [&] {
Settings::values.toggle_framelimit = !Settings::values.toggle_framelimit;
UpdateStatusBar();
});
}
void GMainWindow::SetDefaultUIGeometry() {