// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <sstream>
#include <string>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/lm/lm.h"
#include "core/hle/service/service.h"
#include "core/memory.h"
namespace Service::LM {
class ILogger final : public ServiceFramework<ILogger> {
public:
ILogger() : ServiceFramework("ILogger") {
static const FunctionInfo functions[] = {
{0x00000000, &ILogger::Initialize, "Initialize"},
{0x00000001, &ILogger::SetDestination, "SetDestination"},
};
RegisterHandlers(functions);
}
private:
struct MessageHeader {
enum Flags : u32_le {
IsHead = 1,
IsTail = 2,
};
enum Severity : u32_le {
Trace,
Info,
Warning,
Error,
Critical,
};
u64_le pid;
u64_le threadContext;
union {
BitField<0, 16, Flags> flags;
BitField<16, 8, Severity> severity;
BitField<24, 8, u32> verbosity;
};
u32_le payload_size;
bool IsHeadLog() const {
return flags & Flags::IsHead;
}
bool IsTailLog() const {
return flags & Flags::IsTail;
}
};
static_assert(sizeof(MessageHeader) == 0x18, "MessageHeader is incorrect size");
/// Log field type
enum class Field : u8 {
Skip = 1,
Message = 2,
Line = 3,
Filename = 4,
Function = 5,
Module = 6,
Thread = 7,
};
/**
* ILogger::Initialize service function
* Inputs:
* 0: 0x00000000
* Outputs:
* 0: ResultCode
*/
void Initialize(Kernel::HLERequestContext& ctx) {
// This function only succeeds - Get that out of the way
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
// Read MessageHeader, despite not doing anything with it right now
MessageHeader header{};
VAddr addr{ctx.BufferDescriptorX()[0].Address()};
const VAddr end_addr{addr + ctx.BufferDescriptorX()[0].size};
Memory::ReadBlock(addr, &header, sizeof(MessageHeader));
addr += sizeof(MessageHeader);
if (header.IsHeadLog()) {
log_stream.str("");
log_stream.clear();
}
// Parse out log metadata
u32 line{};
std::string module;
std::string message;
std::string filename;
std::string function;
std::string thread;
while (addr < end_addr) {
const Field field{static_cast<Field>(Memory::Read8(addr++))};
const std::size_t length{Memory::Read8(addr++)};
if (static_cast<Field>(Memory::Read8(addr)) == Field::Skip) {
++addr;
}
switch (field) {
case Field::Skip:
break;
case Field::Message:
message = Memory::ReadCString(addr, length);
break;
case Field::Line:
line = Memory::Read32(addr);
break;
case Field::Filename:
filename = Memory::ReadCString(addr, length);
break;
case Field::Function:
function = Memory::ReadCString(addr, length);
break;
case Field::Module:
module = Memory::ReadCString(addr, length);
break;
case Field::Thread:
thread = Memory::ReadCString(addr, length);
break;
}
addr += length;
}
// Empty log - nothing to do here
if (log_stream.str().empty() && message.empty()) {
return;
}
// Format a nicely printable string out of the log metadata
if (!filename.empty()) {
log_stream << filename << ':';
}
if (!module.empty()) {
log_stream << module << ':';
}
if (!function.empty()) {
log_stream << function << ':';
}
if (line) {
log_stream << std::to_string(line) << ':';
}
if (!thread.empty()) {
log_stream << thread << ':';
}
if (log_stream.str().length() > 0 && log_stream.str().back() == ':') {
log_stream << ' ';
}
log_stream << message;
if (header.IsTailLog()) {
switch (header.severity) {
case MessageHeader::Severity::Trace:
LOG_DEBUG(Debug_Emulated, "{}", log_stream.str());
break;
case MessageHeader::Severity::Info:
LOG_INFO(Debug_Emulated, "{}", log_stream.str());
break;
case MessageHeader::Severity::Warning:
LOG_WARNING(Debug_Emulated, "{}", log_stream.str());
break;
case MessageHeader::Severity::Error:
LOG_ERROR(Debug_Emulated, "{}", log_stream.str());
break;
case MessageHeader::Severity::Critical:
LOG_CRITICAL(Debug_Emulated, "{}", log_stream.str());
break;
}
}
}
// This service function is intended to be used as a way to
// redirect logging output to different destinations, however,
// given we always want to see the logging output, it's sufficient
// to do nothing and return success here.
void SetDestination(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_LM, "called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
std::ostringstream log_stream;
};
class LM final : public ServiceFramework<LM> {
public:
explicit LM() : ServiceFramework{"lm"} {
static const FunctionInfo functions[] = {
{0x00000000, &LM::OpenLogger, "OpenLogger"},
};
RegisterHandlers(functions);
}
/**
* LM::OpenLogger service function
* Inputs:
* 0: 0x00000000
* Outputs:
* 0: ResultCode
*/
void OpenLogger(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_LM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILogger>();
}
};
void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<LM>()->InstallAsService(service_manager);
}
} // namespace Service::LM