1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Implemented basic Overwatch parsing

This commit is contained in:
SpoinkyNL 2016-05-29 14:20:30 +02:00
parent f1b7c667a7
commit 0b889c3b0c
14 changed files with 219 additions and 329 deletions

View File

@ -437,6 +437,7 @@
<Compile Include="Styles\DropTargetAdorners\DropTargetMetroHighlightAdorner.cs" />
<Compile Include="Styles\DropTargetAdorners\DropTargetMetroInsertionAdorner.cs" />
<Compile Include="Utilities\ColorHelpers.cs" />
<Compile Include="Utilities\DataReaders\MmfReader.cs" />
<Compile Include="Utilities\ExtensionMethods.cs" />
<Compile Include="Utilities\GameState\GameDataReceivedEventArgs.cs" />
<Compile Include="Utilities\GameState\GameStateWebServer.cs" />

View File

@ -2,6 +2,7 @@
using Artemis.DeviceProviders.Razer.Utilities;
using Corale.Colore.Core;
using Corale.Colore.Razer;
using Corale.Colore.Razer.Keyboard;
using Constants = Corale.Colore.Razer.Keyboard.Constants;
namespace Artemis.DeviceProviders.Razer
@ -42,7 +43,6 @@ namespace Artemis.DeviceProviders.Razer
public override void DrawBitmap(Bitmap bitmap)
{
var razerArray = RazerUtilities.BitmapColorArray(bitmap, Height, Width);
Chroma.Instance.Keyboard.SetCustom(razerArray);
}
}

View File

@ -155,7 +155,7 @@ namespace Artemis.Managers
headset.UpdateDevice(renderEffect.GenerateHeadsetBrush());
// debugging TODO: Disable when window isn't shown (in Debug VM, or get rid of it, w/e)
_events.PublishOnUIThread(new ChangeBitmap(bitmap));
//_events.PublishOnUIThread(new ChangeBitmap(bitmap));
}
}
}

View File

@ -4,5 +4,14 @@ namespace Artemis.Modules.Games.Overwatch
{
public class OverwatchDataModel : IGameDataModel
{
public OverwatchStatus Status { get; set; }
}
public enum OverwatchStatus
{
Unkown,
InMainMenu,
InCharacterSelection,
InGame
}
}

View File

@ -1,25 +1,37 @@
using System.Drawing;
using System.IO;
using System.IO.MemoryMappedFiles;
using System;
using System.Drawing;
using System.Linq.Dynamic;
using Artemis.Events;
using Artemis.Managers;
using Artemis.Models;
using Artemis.Models.Profiles;
using Artemis.Utilities;
using Artemis.Utilities.DataReaders;
using Caliburn.Micro;
using Brush = System.Windows.Media.Brush;
using Color = System.Windows.Media.Color;
namespace Artemis.Modules.Games.Overwatch
{
public class OverwatchModel : GameModel
{
public OverwatchModel(MainManager mainManager, OverwatchSettings settings)
private readonly IEventAggregator _events;
public OverwatchModel(IEventAggregator events, MainManager mainManager, OverwatchSettings settings)
: base(mainManager, settings, new OverwatchDataModel())
{
_events = events;
Name = "Overwatch";
ProcessName = "notepad";
ProcessName = "Overwatch";
Scale = 4;
Enabled = Settings.Enabled;
Initialized = false;
MmfReader = new MmfReader("overwatchMmf");
}
public MmfReader MmfReader { get; set; }
public int Scale { get; set; }
public override void Dispose()
@ -35,41 +47,27 @@ namespace Artemis.Modules.Games.Overwatch
public override void Update()
{
var gameDataModel = (OverwatchDataModel) GameDataModel;
var mffData = ReadMmf("overwatchMmf");
if (mffData == null)
var colors = MmfReader.GetColorArray();
if (colors == null)
return;
var data = mffData.Split(' ');
}
private string ReadMmf(string overwatchmff)
{
try
var bitmap = new Bitmap(22, 6);
using (var g = Graphics.FromImage(bitmap))
{
// opening not-persistent, pagefile-based memory-mapped file
using (var mmf = MemoryMappedFile.OpenExisting(overwatchmff))
for (var y = 0; y < 6; y++)
{
// open the stream to read from the file
using (var stream = mmf.CreateViewStream())
for (var x = 0; x < 22; x++)
{
// Read from the shared memory, just for this example we know there is a string
var reader = new BinaryReader(stream);
var res = string.Empty;
string str;
do
{
str = reader.ReadString();
if (!string.IsNullOrEmpty(str) && str[0] != 0)
res = res + str;
} while (!string.IsNullOrEmpty(str));
return res;
g.DrawRectangle(new Pen(ColorHelpers.ToDrawingColor(colors[y, x])), y, x, 1, 1 );
}
}
}
catch (FileNotFoundException)
{
return null;
//ignored
}
_events.PublishOnUIThread(new ChangeBitmap(bitmap));
if (colors[0, 0].Equals(Color.FromRgb(55, 30, 0)))
gameDataModel.Status = OverwatchStatus.InMainMenu;
else if (colors[0, 0].Equals(Color.FromRgb(3, 5, 11)))
gameDataModel.Status = OverwatchStatus.InGame;
}
public override Bitmap GenerateBitmap()

View File

@ -8,7 +8,7 @@ namespace Artemis.Modules.Games.Overwatch
public sealed class OverwatchViewModel : GameViewModel
{
public OverwatchViewModel(MainManager main, IEventAggregator events, IProfileEditorViewModelFactory pFactory)
: base(main, new OverwatchModel(main, new OverwatchSettings()), events, pFactory)
: base(main, new OverwatchModel(events, main, new OverwatchSettings()), events, pFactory)
{
DisplayName = "Overwatch";
MainManager.EffectManager.EffectModels.Add(GameModel);

View File

@ -0,0 +1,96 @@
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Text;
using System.Windows.Media;
namespace Artemis.Utilities.DataReaders
{
/// <summary>
/// A helper class for reading memory managed files
/// </summary>
public class MmfReader
{
private DateTime _lastFailure;
public MmfReader(string mmfName)
{
MmfName = mmfName;
}
public string MmfName { get; set; }
/// <summary>
/// Turns the MMF into an color array
/// </summary>
/// <returns></returns>
public Color[,] GetColorArray()
{
var mffString = ReadMmf(MmfName);
if (string.IsNullOrEmpty(mffString))
return null;
var intermediateArray = mffString.Split('|');
if (intermediateArray[0] == "1")
return null;
var array = intermediateArray[1].Substring(1).Split(' ');
if (!array.Any())
return null;
try
{
var colors = new Color[6, 22];
foreach (var intermediate in array)
{
if (intermediate.Length > 16)
continue;
// Can't parse to a byte directly since it may contain values >254
var parts = intermediate.Split(',').Select(int.Parse).ToArray();
if (parts[0] >= 5 && parts[1] >= 21)
continue;
colors[parts[0], parts[1]] = Color.FromRgb((byte) parts[2], (byte) parts[3], (byte) parts[4]);
}
return colors;
}
catch (FormatException)
{
return null;
}
}
/// <summary>
/// Reads the contents of the given MFF into a string
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
private string ReadMmf(string fileName)
{
// Don't read the file within one second after failing
//if (DateTime.Now - _lastFailure > new TimeSpan(0, 0, 1))
// return null;
try
{
using (var mmf = MemoryMappedFile.OpenExisting(fileName))
{
using (var stream = mmf.CreateViewStream())
{
using (var binReader = new BinaryReader(stream))
{
var allBytes = binReader.ReadBytes((int) stream.Length);
return Encoding.UTF8.GetString(allBytes, 0, allBytes.Length);
}
}
}
}
catch (FileNotFoundException)
{
_lastFailure = DateTime.Now;
return null;
//ignored
}
}
}
}

View File

@ -1,104 +0,0 @@
// Original work by VRocker https://github.com/VRocker/LogiLed2Corsair
// I'm mainly a C# developer, and these modification aren't a piece of art, but it suits our needs.
// The MIT License (MIT)
//
// Copyright (c) 2015 VRocker
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "Logger.h"
#include <time.h>
#include <stdlib.h>
#include <stdarg.h>
// Ok this looks butt-ugleh but its just to force the default log level to debug if we compile in debug
#ifdef _DEBUG
LogLevel CLogger::m_eLogLevel = LogLevel::Debug;
#else
LogLevel CLogger::m_eLogLevel = LogLevel::All;
#endif
FILE* CLogger::m_pFile = 0;
void CLogger::InitLogging(const char* szFile)
{
if (!m_pFile)
{
m_pFile = fopen(szFile, "a+");
if (!m_pFile)
{
// Hum, we couldn't open the file for writing
printf("ERROR: Unable to open log file %s.\n", szFile);
}
}
}
void CLogger::EndLogging(void)
{
if (m_pFile)
{
fclose(m_pFile);
m_pFile = 0;
}
}
void CLogger::OutputLog_s(const char* sz, const LogLevel eLevel)
{
// If the level of this log entry is less important than what we are told to log, just return
if (eLevel < m_eLogLevel)
return;
// If we don't have a file to write to, bail.
if (!m_pFile)
return;
char szOutput[512] = { 0 };
time_t rawtime;
struct tm* timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
// Timestamp the log entry
size_t uiLen = sprintf(szOutput, "<%.2d/%.2d/%.2d - %.2d:%.2d:%.2d> %s\n", timeinfo->tm_mday, timeinfo->tm_mon + 1, timeinfo->tm_year + 1900, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, sz);
// Write the text to the file
fwrite(szOutput, 1, uiLen, m_pFile);
// Flush the log file to the disk. May move this to a seperate function
fflush(m_pFile);
// Output the text to any console that may be attached
printf("%s", szOutput);
}
void CLogger::OutputLog(const char* sz, const LogLevel eLevel, ...)
{
char szText[1024] = { 0 };
va_list marker;
va_start(marker, eLevel);
vsprintf(szText, sz, marker);
va_end(marker);
// Since this function is pretty much the same as the safer function, we'll just redirect it there
OutputLog_s(szText, eLevel);
}

View File

@ -1,82 +0,0 @@
// Original work by VRocker https://github.com/VRocker/LogiLed2Corsair
// I'm mainly a C# developer, and these modification aren't a piece of art, but it suits our needs.
// The MIT License (MIT)
//
// Copyright (c) 2015 VRocker
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#ifndef _LOGGER_H
#define _LOGGER_H
#include <stdio.h>
enum class LogLevel
{
Debug,
All,
Critical,
Warning,
Information,
User,
Internal,
None,
// Just to force the compiler to treat the enum options as an int
FORCE_32BIT = 0x7fffffff
};
class CLogger
{
public:
static LogLevel GetLogLevel(void)
{
return m_eLogLevel;
}
static void SetLogLevel(const LogLevel e)
{
#ifndef _DEBUG
if (e == LogLevel::Debug) return;
#endif
m_eLogLevel = e;
}
static void InitLogging(const char* szFile);
// Always remember to end logging when you are finished otherwise you will have file handles floating around
static void EndLogging(void);
// Output the text to a log file.
static void OutputLog_s(const char* sz, const LogLevel eLevel);
static void OutputLog(const char* sz, const LogLevel eLevel, ...);
static FILE* GetFile()
{
return m_pFile;
}
private:
static LogLevel m_eLogLevel;
static FILE* m_pFile;
};
#endif

View File

@ -126,11 +126,9 @@
<None Include="Razer2Artemis.def" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="Logger.cpp" />
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Logger.h" />
<ClInclude Include="main.h" />
<ClInclude Include="RzChromaSDKDefines.h" />
<ClInclude Include="RzChromaSDKTypes.h" />

View File

@ -23,17 +23,11 @@
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Logger.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="main.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Logger.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RzChromaSDKDefines.h">
<Filter>Header Files</Filter>
</ClInclude>

View File

@ -1,212 +1,165 @@
// Original work by VRocker https://github.com/VRocker/LogiLed2Corsair
// I'm mainly a C# developer, and these modification aren't a piece of art, but it suits our needs.
// The MIT License (MIT)
//
// Copyright (c) 2015 VRocker
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "main.h"
#include <stdio.h>
#include <tchar.h>
#include <thread>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include "Logger.h"
#include <filesystem>
#include <sstream>
TCHAR szName[] = TEXT("overwatchMmf");
TCHAR szMsg[] = TEXT("Message from first process.");
#define BUF_SIZE 2000
#define WIN32_LEAN_AND_MEAN
#define BUF_SIZE 4096
static bool g_hasInitialised = false;
const char* game = "";
void cleanup()
{
CLogger::EndLogging();
}
HANDLE hMapFile;
BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID)
{
switch (fdwReason)
if (fdwReason == DLL_PROCESS_ATTACH)
{
case DLL_PROCESS_ATTACH:
{
atexit(cleanup);
// Get the process that loaded the DLL
TCHAR overwatchFind[] = _T("Overwatch");
TCHAR szPath[MAX_PATH];
GetModuleFileName(nullptr, szPath, MAX_PATH);
CLogger::InitLogging("Log.txt");
CLogger::SetLogLevel(LogLevel::Debug);
if (_tcscmp(szPath, overwatchFind) != 0)
game = "overwatch";
// Get the process that loaded the DLL
TCHAR overwatchFind[] = _T("Overwatch");
TCHAR szPath[MAX_PATH];
GetModuleFileName(nullptr, szPath, MAX_PATH);
if (_tcscmp(szPath, overwatchFind) != 0)
game = "overwatch";
CLogger::OutputLog("Attached to process.", LogLevel::Debug);
// Setup mmf
hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, BUF_SIZE, szName);
if (hMapFile == nullptr)
{
CLogger::OutputLog("Could not create file mapping object (error %i)", LogLevel::Debug, GetLastError());
}
}
break;
case DLL_PROCESS_DETACH:
{
CLogger::OutputLog_s("Detached from process.", LogLevel::Debug);
CloseHandle(hMapFile);
cleanup();
}
break;
// Setup mmf
hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, BUF_SIZE, szName);
}
return true;
}
void WriteMmf(const char* msg)
template <typename ... Args>
std::string string_format(const std::string& format, Args ... args)
{
size_t size = snprintf(nullptr, 0, format.c_str(), args ...) + 1; // Extra space for '\0'
std::unique_ptr<char[]> buf(new char[size]);
snprintf(buf.get(), size, format.c_str(), args ...);
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}
void WriteMmf(std::string msg)
{
if (hMapFile == nullptr)
{
CLogger::OutputLog_s("Could not write to mmf", LogLevel::Debug);
return;
}
LPCTSTR pBuf;
pBuf = static_cast<LPTSTR>(MapViewOfFile(hMapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
BUF_SIZE));
pBuf = static_cast<LPTSTR>(MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE));
if (pBuf == nullptr)
{
CLogger::OutputLog("Could not map view of file (error %i)", LogLevel::Debug, GetLastError());
CloseHandle(hMapFile);
return;
}
CopyMemory((PVOID)pBuf, msg, (_tcslen(msg) * sizeof(TCHAR)));
CopyMemory((PVOID)pBuf, msg.c_str(), msg.size());
UnmapViewOfFile(pBuf);
}
RZRESULT Init()
{
CLogger::OutputLog_s("Razer Init called.", LogLevel::Debug);
g_hasInitialised = true;
return 0;
}
RZRESULT UnInit()
{
CLogger::OutputLog_s("Razer UnInit called.", LogLevel::Debug);
return 0;
}
RZRESULT CreateEffect(RZDEVICEID DeviceId, ChromaSDK::EFFECT_TYPE Effect, PRZPARAM pParam, RZEFFECTID* pEffectId)
{
CLogger::OutputLog_s("Razer CreateEffect called.", LogLevel::Debug);
return 0;
}
RZRESULT CreateKeyboardEffect(ChromaSDK::Keyboard::EFFECT_TYPE Effect, PRZPARAM pParam, RZEFFECTID* pEffectId)
{
std::ostringstream os;
auto keys = *static_cast<struct CUSTOM_KEY_EFFECT_TYPE*>(pParam);
std::string res = "";
if (Effect == Keyboard::CHROMA_CUSTOM) {
res += "0|";
auto keys = *static_cast<struct Keyboard::CUSTOM_EFFECT_TYPE*>(pParam);
for (auto y = 0; y < 6; y++)
{
for (auto x = 0; x < 22; x++)
for (auto y = 0; y < 6; y++)
{
auto r = (int) GetRValue(keys.Color[y][x]);
auto g = (int) GetGValue(keys.Color[y][x]);
auto b = (int) GetGValue(keys.Color[y][x]);
os << " [" << x << "][" << y << "](" << r << "," << g << "," << b << ")";
for (auto x = 0; x < 22; x++)
{
auto r = GetRValue(keys.Color[y][x]);
auto g = GetGValue(keys.Color[y][x]);
auto b = GetBValue(keys.Color[y][x]);
res += string_format(" %d,%d,%d,%d,%d", y, x, r, g, b);
}
}
}
else if (Effect == Keyboard::CHROMA_CUSTOM_KEY) {
res += "1|";
auto keys = *static_cast<struct Keyboard::CUSTOM_KEY_EFFECT_TYPE*>(pParam);
for (auto y = 0; y < 6; y++)
{
for (auto x = 0; x < 22; x++)
{
auto r = GetRValue(keys.Color[y][x]);
auto g = GetGValue(keys.Color[y][x]);
auto b = GetBValue(keys.Color[y][x]);
res += string_format(" %d,%d,%d,%d,%d", y, x, r, g, b);
}
}
}
CLogger::OutputLog_s("Razer CreateKeyboardEffect called", LogLevel::Debug);
WriteMmf(os.str().c_str());
WriteMmf(res);
return 0;
}
RZRESULT CreateMouseEffect(ChromaSDK::Mouse::EFFECT_TYPE Effect, PRZPARAM pParam, RZEFFECTID* pEffectId)
{
CLogger::OutputLog_s("Razer CreateMouseEffect called.", LogLevel::Debug);
return 0;
}
RZRESULT CreateHeadsetEffect(ChromaSDK::Headset::EFFECT_TYPE Effect, PRZPARAM pParam, RZEFFECTID* pEffectId)
{
CLogger::OutputLog_s("Razer CreateHeadsetEffect called.", LogLevel::Debug);
return 0;
}
RZRESULT CreateMousepadEffect(ChromaSDK::Mousepad::EFFECT_TYPE Effect, PRZPARAM pParam, RZEFFECTID* pEffectId)
{
CLogger::OutputLog_s("Razer CreateMousepadEffect called.", LogLevel::Debug);
return 0;
}
RZRESULT CreateKeypadEffect(ChromaSDK::Keypad::EFFECT_TYPE Effect, PRZPARAM pParam, RZEFFECTID* pEffectId)
{
CLogger::OutputLog_s("Razer CreateKeypadEffect called.", LogLevel::Debug);
return 0;
}
RZRESULT DeleteEffect(RZEFFECTID EffectId)
{
CLogger::OutputLog_s("Razer DeleteEffect called.", LogLevel::Debug);
return 0;
}
RZRESULT SetEffect(RZEFFECTID EffectId)
{
CLogger::OutputLog_s("Razer SetEffect called.", LogLevel::Debug);
return 0;
}
RZRESULT RegisterEventNotification(HWND hWnd)
{
CLogger::OutputLog_s("Razer RegisterEventNotification called.", LogLevel::Debug);
return 0;
}
RZRESULT UnregisterEventNotification()
{
CLogger::OutputLog_s("Razer UnregisterEventNotification called.", LogLevel::Debug);
return 0;
}
RZRESULT QueryDevice(RZDEVICEID DeviceId, DEVICE_INFO_TYPE& DeviceInfo)
{
CLogger::OutputLog_s("Razer QueryDevice called.", LogLevel::Debug);
return 0;
}

View File

@ -0,0 +1,15 @@
#include <memory>
#include <iostream>
#include <string>
#include <cstdio>
using namespace std;
template <typename ... Args>
string string_format(const std::string& format, Args ... args)
{
size_t size = snprintf(nullptr, 0, format.c_str(), args ...) + 1; // Extra space for '\0'
unique_ptr<char[]> buf(new char[size]);
snprintf(buf.get(), size, format.c_str(), args ...);
return string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <memory>
template <typename ... Args>
std::string string_format(const std::string& format, Args ... args)
{
size_t size = snprintf(nullptr, 0, format.c_str(), args ...) + 1; // Extra space for '\0'
std::unique_ptr<char[]> buf(new char[size]);
snprintf(buf.get(), size, format.c_str(), args ...);
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}