mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-05-21 02:45:00 +00:00
src: Move certain headers in common
This commit is contained in:
parent
6e28ac711f
commit
17aefc1aef
73 changed files with 98 additions and 106 deletions
7
src/common/debug.h
Normal file
7
src/common/debug.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#ifdef _MSC_VER
|
||||
#define BREAKPOINT __debugbreak
|
||||
#elif defined(__GNUC__)
|
||||
#define BREAKPOINT __builtin_trap
|
||||
#else
|
||||
#error What the fuck is this compiler
|
||||
#endif
|
35
src/common/disassembler.cpp
Normal file
35
src/common/disassembler.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include "common/disassembler.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
Disassembler::Disassembler()
|
||||
{
|
||||
ZydisDecoderInit(&m_decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
|
||||
ZydisFormatterInit(&m_formatter, ZYDIS_FORMATTER_STYLE_INTEL);
|
||||
}
|
||||
|
||||
Disassembler::~Disassembler()
|
||||
{
|
||||
}
|
||||
|
||||
void Disassembler::printInstruction(void* code,u64 address)//print a single instruction
|
||||
{
|
||||
ZydisDecodedInstruction instruction;
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT_VISIBLE];
|
||||
ZyanStatus status = ZydisDecoderDecodeFull(&m_decoder, code, sizeof(code), &instruction, operands);
|
||||
if (!ZYAN_SUCCESS(status))
|
||||
{
|
||||
fmt::print("decode instruction failed at {}\n", fmt::ptr(code));
|
||||
}
|
||||
else
|
||||
{
|
||||
printInst(instruction, operands,address);
|
||||
}
|
||||
}
|
||||
|
||||
void Disassembler::printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands,u64 address)
|
||||
{
|
||||
const int bufLen = 256;
|
||||
char szBuffer[bufLen];
|
||||
ZydisFormatterFormatInstruction(&m_formatter, &inst, operands,inst.operand_count_visible, szBuffer, sizeof(szBuffer), address, ZYAN_NULL);
|
||||
fmt::print("instruction: {}\n", szBuffer);
|
||||
}
|
17
src/common/disassembler.h
Normal file
17
src/common/disassembler.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <Zydis/Zydis.h>
|
||||
#include "common/types.h"
|
||||
|
||||
class Disassembler
|
||||
{
|
||||
public:
|
||||
Disassembler();
|
||||
~Disassembler();
|
||||
void printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands,u64 address);
|
||||
void printInstruction(void* code,u64 address);
|
||||
|
||||
private:
|
||||
ZydisDecoder m_decoder;
|
||||
ZydisFormatter m_formatter;
|
||||
};
|
37
src/common/discord.cpp
Normal file
37
src/common/discord.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include "discord.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
void Discord::RPC::init() {
|
||||
DiscordEventHandlers handlers{};
|
||||
Discord_Initialize("1139939140494971051", &handlers, 1, nullptr);
|
||||
|
||||
startTimestamp = time(nullptr);
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void Discord::RPC::update(Discord::RPCStatus status, const std::string& game) {
|
||||
DiscordRichPresence rpc{};
|
||||
|
||||
if (status == Discord::RPCStatus::Playing) {
|
||||
rpc.details = "Playing a game";
|
||||
rpc.state = game.c_str();
|
||||
} else {
|
||||
rpc.details = "Idle";
|
||||
}
|
||||
|
||||
rpc.largeImageKey = "shadps4";
|
||||
rpc.largeImageText = "ShadPS4 is a PS4 emulator";
|
||||
rpc.startTimestamp = startTimestamp;
|
||||
|
||||
Discord_UpdatePresence(&rpc);
|
||||
}
|
||||
|
||||
void Discord::RPC::stop() {
|
||||
if (enabled) {
|
||||
enabled = false;
|
||||
Discord_ClearPresence();
|
||||
Discord_Shutdown();
|
||||
}
|
||||
}
|
19
src/common/discord.h
Normal file
19
src/common/discord.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <discord_rpc.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Discord {
|
||||
enum class RPCStatus { Idling, Playing };
|
||||
|
||||
class RPC {
|
||||
std::uint64_t startTimestamp;
|
||||
bool enabled = false;
|
||||
|
||||
public:
|
||||
void init();
|
||||
void update(RPCStatus status, const std::string& title);
|
||||
void stop();
|
||||
};
|
||||
} // namespace Discord
|
93
src/common/fs_file.cpp
Normal file
93
src/common/fs_file.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
#include "common/fs_file.h"
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
File::File() = default;
|
||||
|
||||
File::File(const std::string& path, OpenMode mode) {
|
||||
open(path, mode);
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool File::open(const std::string& path, OpenMode mode) {
|
||||
close();
|
||||
#ifdef _WIN64
|
||||
fopen_s(&m_file, path.c_str(), getOpenMode(mode));
|
||||
#else
|
||||
m_file = std::fopen(path.c_str(), getOpenMode(mode));
|
||||
#endif
|
||||
return isOpen();
|
||||
}
|
||||
|
||||
bool File::close() {
|
||||
if (!isOpen() || std::fclose(m_file) != 0) [[unlikely]] {
|
||||
m_file = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_file = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::write(std::span<const u08> data) {
|
||||
return isOpen() && std::fwrite(data.data(), 1, data.size(), m_file) == data.size();
|
||||
}
|
||||
|
||||
bool File::read(void* data, u64 size) const {
|
||||
return isOpen() && std::fread(data, 1, size, m_file) == size;
|
||||
}
|
||||
|
||||
bool File::seek(s64 offset, SeekMode mode) {
|
||||
#ifdef _WIN64
|
||||
if (!isOpen() || _fseeki64(m_file, offset, getSeekMode(mode)) != 0) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!isOpen() || fseeko64(m_file, offset, getSeekMode(mode)) != 0) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 File::tell() const {
|
||||
if (isOpen()) [[likely]] {
|
||||
#ifdef _WIN64
|
||||
return _ftelli64(m_file);
|
||||
#else
|
||||
return ftello64(m_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
u64 File::getFileSize() {
|
||||
#ifdef _WIN64
|
||||
const u64 pos = _ftelli64(m_file);
|
||||
if (_fseeki64(m_file, 0, SEEK_END) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const u64 size = _ftelli64(m_file);
|
||||
if (_fseeki64(m_file, pos, SEEK_SET) != 0) {
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
const u64 pos = ftello64(m_file);
|
||||
if (fseeko64(m_file, 0, SEEK_END) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const u64 size = ftello64(m_file);
|
||||
if (fseeko64(m_file, pos, SEEK_SET) != 0) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace Common::FS
|
87
src/common/fs_file.h
Normal file
87
src/common/fs_file.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
enum class OpenMode : u32 {
|
||||
Read = 0x1,
|
||||
Write = 0x2,
|
||||
ReadWrite = Read | Write
|
||||
};
|
||||
|
||||
enum class SeekMode : u32 {
|
||||
Set,
|
||||
Cur,
|
||||
End,
|
||||
};
|
||||
|
||||
class File {
|
||||
public:
|
||||
File();
|
||||
explicit File(const std::string& path, OpenMode mode = OpenMode::Read);
|
||||
~File();
|
||||
|
||||
bool open(const std::string& path, OpenMode mode = OpenMode::Read);
|
||||
bool close();
|
||||
bool read(void* data, u64 size) const;
|
||||
bool write(std::span<const u08> data);
|
||||
bool seek(s64 offset, SeekMode mode);
|
||||
u64 getFileSize();
|
||||
u64 tell() const;
|
||||
|
||||
template <typename T>
|
||||
bool read(T& data) const {
|
||||
return read(&data, sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool read(std::vector<T>& data) const {
|
||||
return read(data.data(), data.size() * sizeof(T));
|
||||
}
|
||||
|
||||
bool isOpen() const {
|
||||
return m_file != nullptr;
|
||||
}
|
||||
|
||||
const char* getOpenMode(OpenMode mode) const {
|
||||
switch (mode) {
|
||||
case OpenMode::Read:
|
||||
return "rb";
|
||||
case OpenMode::Write:
|
||||
return "wb";
|
||||
case OpenMode::ReadWrite:
|
||||
return "r+b";
|
||||
default:
|
||||
return "r";
|
||||
}
|
||||
}
|
||||
|
||||
int getSeekMode(SeekMode mode) const {
|
||||
switch (mode) {
|
||||
case SeekMode::Set:
|
||||
return SEEK_SET;
|
||||
case SeekMode::Cur:
|
||||
return SEEK_CUR;
|
||||
case SeekMode::End:
|
||||
return SEEK_END;
|
||||
default:
|
||||
return SEEK_SET;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::FILE* fileDescr() const {
|
||||
return m_file;
|
||||
}
|
||||
|
||||
private:
|
||||
std::FILE* m_file{};
|
||||
};
|
||||
|
||||
} // namespace Common::FS
|
90
src/common/log.cpp
Normal file
90
src/common/log.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#include <spdlog/common.h>
|
||||
#include <spdlog/pattern_formatter.h>
|
||||
#include <spdlog/sinks/basic_file_sink.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <vector>
|
||||
#include <Util/config.h>
|
||||
|
||||
#ifdef _WIN64
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace logging {
|
||||
std::vector<spdlog::sink_ptr> sinks;
|
||||
constexpr bool log_file_exceptions = true; // disable it to disable logging
|
||||
|
||||
void flush() { spdlog::details::registry::instance().flush_all(); }
|
||||
|
||||
#ifdef _WIN64
|
||||
|
||||
static LONG WINAPI exception_handler(PEXCEPTION_POINTERS pExp) noexcept {
|
||||
const u32 ec = pExp->ExceptionRecord->ExceptionCode;
|
||||
switch (ec) {
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
LOG_CRITICAL_IF(log_file_exceptions,"Exception EXCEPTION_ACCESS_VIOLATION ({}). ", log_hex(ec));
|
||||
switch (pExp->ExceptionRecord->ExceptionInformation[0]) {
|
||||
case 0: LOG_CRITICAL_IF(log_file_exceptions,"Read violation at address {}.", log_hex(pExp->ExceptionRecord->ExceptionInformation[1])); break;
|
||||
case 1: LOG_CRITICAL_IF(log_file_exceptions,"Write violation at address {}.", log_hex(pExp->ExceptionRecord->ExceptionInformation[1])); break;
|
||||
case 8:LOG_CRITICAL_IF(log_file_exceptions,"DEP violation at address {}.", log_hex(pExp->ExceptionRecord->ExceptionInformation[1])); break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: LOG_CRITICAL_IF(log_file_exceptions,"Exception EXCEPTION_ARRAY_BOUNDS_EXCEEDED ({:#x}). ", ec); break;
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT: LOG_CRITICAL_IF(log_file_exceptions,"Exception EXCEPTION_DATATYPE_MISALIGNMENT ({}). ", log_hex(ec)); break;
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO: LOG_CRITICAL_IF(log_file_exceptions,"Exception EXCEPTION_FLT_DIVIDE_BY_ZERO ({}). ", log_hex(ec)); break;
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION: LOG_CRITICAL_IF(log_file_exceptions,"Exception EXCEPTION_ILLEGAL_INSTRUCTION ({}). ", log_hex(ec)); break;
|
||||
case EXCEPTION_IN_PAGE_ERROR: LOG_CRITICAL_IF(log_file_exceptions,"Exception EXCEPTION_IN_PAGE_ERROR ({}). ", log_hex(ec)); break;
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO: LOG_CRITICAL_IF(log_file_exceptions,"Exception EXCEPTION_INT_DIVIDE_BY_ZERO ({}). ", log_hex(ec)); break;
|
||||
case EXCEPTION_PRIV_INSTRUCTION: LOG_CRITICAL_IF(log_file_exceptions,"Exception EXCEPTION_PRIV_INSTRUCTION ({}). ", log_hex(ec)); break;
|
||||
case EXCEPTION_STACK_OVERFLOW: LOG_CRITICAL_IF(log_file_exceptions,"Exception EXCEPTION_STACK_OVERFLOW ({}). ", log_hex(ec)); break;
|
||||
default: return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
flush();
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void register_log_exception_handler() {
|
||||
if (!AddVectoredExceptionHandler(0, exception_handler)) {
|
||||
LOG_CRITICAL_IF(log_file_exceptions,"Failed to register an exception handler");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
int init(bool use_stdout) {
|
||||
sinks.clear(); // clear existing sinks
|
||||
if (use_stdout) // if we use stdout window then init it as well
|
||||
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
|
||||
sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(L"shadps4.txt", true));
|
||||
spdlog::set_default_logger(std::make_shared<spdlog::logger>("shadps4 logger", begin(sinks), end(sinks)));
|
||||
auto f = std::make_unique<spdlog::pattern_formatter>("%^|%L|: %v%$", spdlog::pattern_time_type::local, std::string("")); // disable eol
|
||||
spdlog::set_formatter(std::move(f));
|
||||
spdlog::set_level(static_cast<spdlog::level::level_enum>(Config::getLogLevel()));
|
||||
spdlog::level::level_enum t = spdlog::get_level();
|
||||
|
||||
#ifdef _WIN64
|
||||
register_log_exception_handler();
|
||||
#endif
|
||||
static std::terminate_handler old_terminate = nullptr;
|
||||
old_terminate = std::set_terminate([]() {
|
||||
try {
|
||||
std::rethrow_exception(std::current_exception());
|
||||
} catch (const std::exception &e) {
|
||||
LOG_CRITICAL_IF(log_file_exceptions, "Unhandled C++ exception. {}", e.what());
|
||||
} catch (...) {
|
||||
LOG_CRITICAL_IF(log_file_exceptions, "Unhandled C++ exception. UNKNOWN");
|
||||
}
|
||||
flush();
|
||||
if (old_terminate) old_terminate();
|
||||
});
|
||||
|
||||
return 0; // all ok
|
||||
}
|
||||
|
||||
void set_level(spdlog::level::level_enum log_level) { spdlog::set_level(log_level); }
|
||||
|
||||
} // namespace logging
|
73
src/common/log.h
Normal file
73
src/common/log.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace logging {
|
||||
|
||||
#define LOG_TRACE SPDLOG_TRACE
|
||||
#define LOG_DEBUG SPDLOG_DEBUG
|
||||
#define LOG_INFO SPDLOG_INFO
|
||||
#define LOG_WARN SPDLOG_WARN
|
||||
#define LOG_ERROR SPDLOG_ERROR
|
||||
#define LOG_CRITICAL SPDLOG_CRITICAL
|
||||
|
||||
#define LOG_TRACE_IF(flag, ...) \
|
||||
if (flag) LOG_TRACE(__VA_ARGS__)
|
||||
#define LOG_DEBUG_IF(flag, ...) \
|
||||
if (flag) LOG_DEBUG(__VA_ARGS__)
|
||||
#define LOG_INFO_IF(flag, ...) \
|
||||
if (flag) LOG_INFO(__VA_ARGS__)
|
||||
#define LOG_WARN_IF(flag, ...) \
|
||||
if (flag) LOG_WARN(__VA_ARGS__)
|
||||
#define LOG_ERROR_IF(flag, ...) \
|
||||
if (flag) LOG_ERROR(__VA_ARGS__)
|
||||
#define LOG_CRITICAL_IF(flag, ...) \
|
||||
if (flag) LOG_CRITICAL(__VA_ARGS__)
|
||||
|
||||
int init(bool use_stdout);
|
||||
void set_level(spdlog::level::level_enum log_level);
|
||||
} // namespace logging
|
||||
|
||||
// copyright vita3k emu https://github.com/Vita3K/Vita3K/blob/master/vita3k/util/include/util/log.h
|
||||
/*
|
||||
returns: A string with the input number formatted in hexadecimal
|
||||
Examples:
|
||||
* `12` returns: `"0xC"`
|
||||
* `1337` returns: `"0x539"`
|
||||
* `72742069` returns: `"0x455F4B5"`
|
||||
*/
|
||||
template <typename T>
|
||||
std::string log_hex(T val) {
|
||||
using unsigned_type = typename std::make_unsigned<T>::type;
|
||||
std::stringstream ss;
|
||||
ss << "0x";
|
||||
ss << std::hex << static_cast<unsigned_type>(val);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/*
|
||||
returns: A string with the input number formatted in hexadecimal with padding of the inputted type size
|
||||
Examples:
|
||||
* `uint8_t 5` returns: `"0x05"`
|
||||
* `uint8_t 15` returns: `"0x0F"`
|
||||
* `uint8_t 255` returns: `"0xFF"`
|
||||
|
||||
* `uint16_t 15` returns: `"0x000F"`
|
||||
* `uint16_t 1337` returns: `"0x0539"`
|
||||
* `uint16_t 65535` returns: `"0xFFFF"`
|
||||
|
||||
|
||||
* `uint32_t 15` returns: `"0x0000000F"`
|
||||
* `uint32_t 1337` returns: `"0x00000539"`
|
||||
* `uint32_t 65535` returns: `"0x0000FFFF"`
|
||||
* `uint32_t 134217728` returns: `"0x08000000"`
|
||||
*/
|
||||
template <typename T>
|
||||
std::string log_hex_full(T val) {
|
||||
std::stringstream ss;
|
||||
ss << "0x";
|
||||
ss << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex << val;
|
||||
return ss.str();
|
||||
}
|
21
src/common/singleton.h
Normal file
21
src/common/singleton.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
template <class T>
|
||||
class singleton {
|
||||
public:
|
||||
static T* instance() {
|
||||
if (!m_instance) {
|
||||
m_instance = std::make_unique<T>();
|
||||
}
|
||||
return m_instance.get();
|
||||
}
|
||||
|
||||
protected:
|
||||
singleton();
|
||||
~singleton();
|
||||
|
||||
private:
|
||||
static inline std::unique_ptr<T> m_instance{};
|
||||
};
|
23
src/common/string_util.cpp
Normal file
23
src/common/string_util.cpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include "common/string_util.h"
|
||||
|
||||
namespace StringUtil {
|
||||
|
||||
std::vector<std::string> split_string(const std::string &str, char delimiter) {
|
||||
std::stringstream str_stream(str);
|
||||
std::string segment;
|
||||
std::vector<std::string> seglist;
|
||||
|
||||
const size_t num_segments = std::count_if(str.begin(), str.end(), [&](char c) { return c == delimiter; }) + (str.empty() ? 1 : 0);
|
||||
|
||||
seglist.reserve(num_segments);
|
||||
|
||||
while (std::getline(str_stream, segment, delimiter)) {
|
||||
seglist.push_back(segment);
|
||||
}
|
||||
return seglist;
|
||||
}
|
||||
|
||||
} // namespace StringUtil
|
9
src/common/string_util.h
Normal file
9
src/common/string_util.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace StringUtil {
|
||||
|
||||
std::vector<std::string> split_string(const std::string& str, char delimiter);
|
||||
|
||||
}
|
23
src/common/types.h
Normal file
23
src/common/types.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
using s08 = std::int8_t;
|
||||
using s16 = std::int16_t;
|
||||
using s32 = std::int32_t;
|
||||
using s64 = std::int64_t;
|
||||
|
||||
using u08 = std::uint8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
using f32 = float;
|
||||
using f64 = double;
|
||||
|
||||
#define PS4_SYSV_ABI __attribute__((sysv_abi))
|
||||
|
||||
|
||||
// UDLs for memory size values
|
||||
constexpr u64 operator""_KB(u64 x) { return 1024ULL * x; }
|
||||
constexpr u64 operator""_MB(u64 x) { return 1024_KB * x; }
|
||||
constexpr u64 operator""_GB(u64 x) { return 1024_MB * x; }
|
7
src/common/version.h
Normal file
7
src/common/version.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Emulator {
|
||||
constexpr char VERSION[] = "0.0.3 WIP";
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue