NES: HD packs load optimizations

This commit is contained in:
Sour 2023-03-02 00:00:55 -05:00
parent 6f525ca639
commit e92685c564
5 changed files with 238 additions and 132 deletions

View file

@ -136,10 +136,26 @@ struct HdScreenInfo
}
};
enum class HdPackConditionType
{
HMirror,
VMirror,
BgPriority,
FrameRange,
MemoryCheck,
MemoryCheckConstant,
TileNearby,
TileAtPos,
SpriteAtPos,
SpriteNearby,
SpritePalette,
};
struct HdPackCondition
{
string Name;
virtual HdPackConditionType GetConditionType() = 0;
virtual string GetConditionName() = 0;
virtual bool IsExcludedFromFile() { return Name.size() > 0 && Name[0] == '!'; }
virtual string ToString() = 0;

View file

@ -102,6 +102,7 @@ struct HdPackBaseMemoryCondition : public HdPackCondition
struct HdPackHorizontalMirroringCondition : public HdPackCondition
{
HdPackConditionType GetConditionType() override { return HdPackConditionType::HMirror; }
string GetConditionName() override { return "hmirror"; }
string ToString() override { return ""; }
bool IsExcludedFromFile() override { return true; }
@ -114,6 +115,7 @@ struct HdPackHorizontalMirroringCondition : public HdPackCondition
struct HdPackVerticalMirroringCondition : public HdPackCondition
{
HdPackConditionType GetConditionType() override { return HdPackConditionType::VMirror; }
string GetConditionName() override { return "vmirror"; }
string ToString() override { return ""; }
bool IsExcludedFromFile() override { return true; }
@ -126,6 +128,7 @@ struct HdPackVerticalMirroringCondition : public HdPackCondition
struct HdPackBgPriorityCondition : public HdPackCondition
{
HdPackConditionType GetConditionType() override { return HdPackConditionType::BgPriority; }
string GetConditionName() override { return "bgpriority"; }
string ToString() override { return ""; }
bool IsExcludedFromFile() override { return true; }
@ -138,6 +141,7 @@ struct HdPackBgPriorityCondition : public HdPackCondition
struct HdPackMemoryCheckCondition : public HdPackBaseMemoryCondition
{
HdPackConditionType GetConditionType() override { return HdPackConditionType::MemoryCheck; }
HdPackMemoryCheckCondition() { _useCache = true; }
string GetConditionName() override { return IsPpuCondition() ? "ppuMemoryCheck" : "memoryCheck"; }
@ -160,6 +164,7 @@ struct HdPackMemoryCheckCondition : public HdPackBaseMemoryCondition
struct HdPackMemoryCheckConstantCondition : public HdPackBaseMemoryCondition
{
HdPackConditionType GetConditionType() override { return HdPackConditionType::MemoryCheckConstant; }
HdPackMemoryCheckConstantCondition() { _useCache = true; }
string GetConditionName() override { return IsPpuCondition() ? "ppuMemoryCheckConstant" : "memoryCheckConstant"; }
@ -187,6 +192,7 @@ struct HdPackFrameRangeCondition : public HdPackCondition
HdPackFrameRangeCondition() { _useCache = true; }
HdPackConditionType GetConditionType() override { return HdPackConditionType::FrameRange; }
string GetConditionName() override { return "frameRange"; }
void Initialize(uint32_t operandA, uint32_t operandB)
@ -214,6 +220,7 @@ struct HdPackFrameRangeCondition : public HdPackCondition
struct HdPackTileAtPositionCondition : public HdPackBaseTileCondition
{
HdPackTileAtPositionCondition() { _useCache = true; }
HdPackConditionType GetConditionType() override { return HdPackConditionType::TileAtPos; }
string GetConditionName() override { return "tileAtPosition"; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
@ -230,6 +237,7 @@ struct HdPackTileAtPositionCondition : public HdPackBaseTileCondition
struct HdPackSpriteAtPositionCondition : public HdPackBaseTileCondition
{
HdPackSpriteAtPositionCondition() { _useCache = true; }
HdPackConditionType GetConditionType() override { return HdPackConditionType::SpriteAtPos; }
string GetConditionName() override { return "spriteAtPosition"; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
@ -252,6 +260,7 @@ struct HdPackSpriteAtPositionCondition : public HdPackBaseTileCondition
struct HdPackTileNearbyCondition : public HdPackBaseTileCondition
{
HdPackConditionType GetConditionType() override { return HdPackConditionType::TileNearby; }
string GetConditionName() override { return "tileNearby"; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
@ -272,6 +281,7 @@ struct HdPackTileNearbyCondition : public HdPackBaseTileCondition
struct HdPackSpriteNearbyCondition : public HdPackBaseTileCondition
{
HdPackConditionType GetConditionType() override { return HdPackConditionType::SpriteNearby; }
string GetConditionName() override { return "spriteNearby"; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
@ -304,6 +314,7 @@ struct HdPackSpriteNearbyCondition : public HdPackBaseTileCondition
template<int paletteId>
struct HdPackSpritePaletteCondition : public HdPackCondition
{
HdPackConditionType GetConditionType() override { return HdPackConditionType::SpritePalette; }
string GetConditionName() override { return "sppalette"; }
string ToString() override { return ""; }
bool IsExcludedFromFile() override { return true; }

View file

@ -11,6 +11,7 @@
#include "Utilities/StringUtilities.h"
#include "Utilities/HexUtilities.h"
#include "Utilities/PNGHelper.h"
#include "Utilities/FastString.h"
#define checkConstraint(x, y) if(!(x)) { MessageManager::Log(y); return; }
@ -118,7 +119,7 @@ bool HdPackLoader::LoadFile(string filename, vector<uint8_t> &fileData)
uint32_t fileSize = (uint32_t)file.tellg();
file.seekg(0, ios::beg);
fileData = vector<uint8_t>(fileSize, 0);
fileData.resize(fileSize);
file.read((char*)fileData.data(), fileSize);
return true;
@ -130,7 +131,7 @@ bool HdPackLoader::LoadFile(string filename, vector<uint8_t> &fileData)
bool HdPackLoader::LoadPack()
{
string currentLine;
string lineContent;
try {
vector<uint8_t> hdDefinition;
if(!LoadFile("hires.txt", hdDefinition)) {
@ -139,7 +140,21 @@ bool HdPackLoader::LoadPack()
InitializeGlobalConditions();
for(string lineContent : StringUtilities::Split(string(hdDefinition.data(), hdDefinition.data() + hdDefinition.size()), '\n')) {
size_t len = hdDefinition.size();
size_t pos = 0;
while(pos < len) {
lineContent.clear();
size_t start = pos;
for(; pos < len; pos++) {
if(hdDefinition[pos] == '\n') {
pos++;
break;
}
}
lineContent.insert(0, (char*)hdDefinition.data() + start, pos < len ? (pos - start - 1) : (len - start));
if(lineContent.empty()) {
continue;
}
@ -147,17 +162,41 @@ bool HdPackLoader::LoadPack()
if(lineContent[lineContent.size() - 1] == '\r') {
lineContent = lineContent.substr(0, lineContent.size() - 1);
}
currentLine = lineContent;
vector<HdPackCondition*> conditions;
if(lineContent.substr(0, 1) == "[") {
size_t endOfCondition = lineContent.find_first_of(']', 1);
if(endOfCondition == string::npos) {
MessageManager::Log("[HDPack] Invalid condition tag: " + lineContent);
continue;
}
conditions = ParseConditionString(lineContent.substr(1, endOfCondition - 1));
lineContent = lineContent.substr(endOfCondition + 1);
}
vector<string> tokens;
if(lineContent.substr(0, 5) == "<ver>") {
if(lineContent.substr(0, 6) == "<tile>") {
tokens = StringUtilities::Split(lineContent.substr(6), ',');
ProcessTileTag(tokens, conditions);
} else if(lineContent.substr(0, 12) == "<background>") {
tokens = StringUtilities::Split(lineContent.substr(12), ',');
ProcessBackgroundTag(tokens, conditions);
} else if(lineContent.substr(0, 11) == "<condition>") {
tokens = StringUtilities::Split(lineContent.substr(11), ',');
ProcessConditionTag(tokens, false);
ProcessConditionTag(tokens, true);
} else if(lineContent.substr(0, 5) == "<img>") {
lineContent = lineContent.substr(5);
if(!ProcessImgTag(lineContent)) {
return false;
}
} else if(lineContent.substr(0, 5) == "<bgm>") {
tokens = StringUtilities::Split(lineContent.substr(5), ',');
ProcessBgmTag(tokens);
} else if(lineContent.substr(0, 5) == "<sfx>") {
tokens = StringUtilities::Split(lineContent.substr(5), ',');
ProcessSfxTag(tokens);
} else if(lineContent.substr(0, 5) == "<ver>") {
_data->Version = stoi(lineContent.substr(5));
if(_data->Version > HdNesPack::CurrentVersion) {
MessageManager::Log("[HDPack] This HD Pack was built with a more recent version of Mesen - update Mesen to the latest version and try again.");
@ -169,33 +208,12 @@ bool HdPackLoader::LoadPack()
} else if(lineContent.substr(0, 10) == "<overscan>") {
tokens = StringUtilities::Split(lineContent.substr(10), ',');
ProcessOverscanTag(tokens);
} else if(lineContent.substr(0, 5) == "<img>") {
lineContent = lineContent.substr(5);
if(!ProcessImgTag(lineContent)) {
return false;
}
} else if(lineContent.substr(0, 7) == "<patch>") {
tokens = StringUtilities::Split(lineContent.substr(7), ',');
ProcessPatchTag(tokens);
} else if(lineContent.substr(0, 12) == "<background>") {
tokens = StringUtilities::Split(lineContent.substr(12), ',');
ProcessBackgroundTag(tokens, conditions);
} else if(lineContent.substr(0, 11) == "<condition>") {
tokens = StringUtilities::Split(lineContent.substr(11), ',');
ProcessConditionTag(tokens, false);
ProcessConditionTag(tokens, true);
} else if(lineContent.substr(0, 6) == "<tile>") {
tokens = StringUtilities::Split(lineContent.substr(6), ',');
ProcessTileTag(tokens, conditions);
} else if(lineContent.substr(0, 9) == "<options>") {
tokens = StringUtilities::Split(lineContent.substr(9), ',');
ProcessOptionTag(tokens);
} else if(lineContent.substr(0, 5) == "<bgm>") {
tokens = StringUtilities::Split(lineContent.substr(5), ',');
ProcessBgmTag(tokens);
} else if(lineContent.substr(0, 5) == "<sfx>") {
tokens = StringUtilities::Split(lineContent.substr(5), ',');
ProcessSfxTag(tokens);
}
}
@ -204,7 +222,7 @@ bool HdPackLoader::LoadPack()
return true;
} catch(std::exception &ex) {
MessageManager::Log(string("[HDPack] Error loading HDPack: ") + ex.what() + " on line: " + currentLine);
MessageManager::Log(string("[HDPack] Error loading HDPack: ") + ex.what() + " on line: " + lineContent);
return false;
}
}
@ -311,15 +329,15 @@ void HdPackLoader::ProcessTileTag(vector<string> &tokens, vector<HdPackCondition
tileInfo->Conditions = conditions;
tileInfo->ForceDisableCache = false;
for(HdPackCondition* condition : conditions) {
if(dynamic_cast<HdPackSpriteNearbyCondition*>(condition)) {
tileInfo->ForceDisableCache = true;
break;
} else if(dynamic_cast<HdPackTileNearbyCondition*>(condition)) {
HdPackTileNearbyCondition* tileNearby = dynamic_cast<HdPackTileNearbyCondition*>(condition);
if(tileNearby->TileX % 8 > 0 || tileNearby->TileY % 8 > 0) {
tileInfo->ForceDisableCache = true;
HdPackConditionType type = condition->GetConditionType();
switch(type){
case HdPackConditionType::SpriteNearby: tileInfo->ForceDisableCache = true; break;
case HdPackConditionType::TileNearby:
HdPackTileNearbyCondition* tileNearby = (HdPackTileNearbyCondition*)condition;
if(tileNearby->TileX % 8 > 0 || tileNearby->TileY % 8 > 0) {
tileInfo->ForceDisableCache = true;
}
break;
}
}
}
@ -415,98 +433,116 @@ void HdPackLoader::ProcessConditionTag(vector<string> &tokens, bool createInvert
}
int index = 2;
if(dynamic_cast<HdPackBaseTileCondition*>(condition.get())) {
checkConstraint(tokens.size() >= 6, "[HDPack] Condition tag should contain at least 6 parameters");
switch(condition->GetConditionType()) {
case HdPackConditionType::TileNearby:
case HdPackConditionType::TileAtPos:
case HdPackConditionType::SpriteNearby:
case HdPackConditionType::SpriteAtPos: {
checkConstraint(tokens.size() >= 6, "[HDPack] Condition tag should contain at least 6 parameters");
int x = std::stoi(tokens[index++]);
int y = std::stoi(tokens[index++]);
string token = tokens[index++];
int32_t tileIndex = -1;
string tileData;
if(token.size() == 32) {
tileData = token;
} else {
if(_data->Version < 104) {
tileIndex = std::stoi(token);
int x = std::stoi(tokens[index++]);
int y = std::stoi(tokens[index++]);
string token = tokens[index++];
int32_t tileIndex = -1;
string tileData;
if(token.size() == 32) {
tileData = token;
} else {
//Tile indexes are stored as hex starting from version 104+
tileIndex = HexUtilities::FromHex(token);
if(_data->Version < 104) {
tileIndex = std::stoi(token);
} else {
//Tile indexes are stored as hex starting from version 104+
tileIndex = HexUtilities::FromHex(token);
}
}
}
uint32_t palette = HexUtilities::FromHex(tokens[index++]);
uint32_t palette = HexUtilities::FromHex(tokens[index++]);
((HdPackBaseTileCondition*)condition.get())->Initialize(x, y, palette, tileIndex, tileData);
} else if(dynamic_cast<HdPackBaseMemoryCondition*>(condition.get())) {
checkConstraint(_data->Version >= 101, "[HDPack] This feature requires version 101+ of HD Packs");
checkConstraint(tokens.size() >= 5, "[HDPack] Condition tag should contain at least 5 parameters");
((HdPackBaseTileCondition*)condition.get())->Initialize(x, y, palette, tileIndex, tileData);
break;
}
case HdPackConditionType::MemoryCheck:
case HdPackConditionType::MemoryCheckConstant: {
checkConstraint(_data->Version >= 101, "[HDPack] This feature requires version 101+ of HD Packs");
checkConstraint(tokens.size() >= 5, "[HDPack] Condition tag should contain at least 5 parameters");
bool usePpuMemory = tokens[1].substr(0, 3) == "ppu";
uint32_t operandA = HexUtilities::FromHex(tokens[index++]);
bool usePpuMemory = tokens[1].substr(0, 3) == "ppu";
uint32_t operandA = HexUtilities::FromHex(tokens[index++]);
if(usePpuMemory) {
checkConstraint(operandA <= 0x3FFF, "[HDPack] Out of range memoryCheck operand");
operandA |= HdPackBaseMemoryCondition::PpuMemoryMarker;
} else {
checkConstraint(operandA <= 0xFFFF, "[HDPack] Out of range memoryCheck operand");
}
HdPackConditionOperator op;
string opString = tokens[index++];
if(opString == "==") {
op = HdPackConditionOperator::Equal;
} else if(opString == "!=") {
op = HdPackConditionOperator::NotEqual;
} else if(opString == ">") {
op = HdPackConditionOperator::GreaterThan;
} else if(opString == "<") {
op = HdPackConditionOperator::LowerThan;
} else if(opString == "<=") {
op = HdPackConditionOperator::LowerThanOrEqual;
} else if(opString == ">=") {
op = HdPackConditionOperator::GreaterThanOrEqual;
} else {
checkConstraint(false, "[HDPack] Invalid operator.");
}
uint32_t operandB = HexUtilities::FromHex(tokens[index++]);
uint32_t mask = 0xFF;
if(tokens.size() > 5 && _data->Version >= 103) {
checkConstraint(operandB <= 0xFF, "[HDPack] Out of range memoryCheck mask");
mask = HexUtilities::FromHex(tokens[index++]);
}
if(dynamic_cast<HdPackMemoryCheckCondition*>(condition.get())) {
if(usePpuMemory) {
checkConstraint(operandB <= 0x3FFF, "[HDPack] Out of range memoryCheck operand");
operandB |= HdPackBaseMemoryCondition::PpuMemoryMarker;
checkConstraint(operandA <= 0x3FFF, "[HDPack] Out of range memoryCheck operand");
operandA |= HdPackBaseMemoryCondition::PpuMemoryMarker;
} else {
checkConstraint(operandB <= 0xFFFF, "[HDPack] Out of range memoryCheck operand");
checkConstraint(operandA <= 0xFFFF, "[HDPack] Out of range memoryCheck operand");
}
_data->WatchedMemoryAddresses.emplace(operandB);
} else if(dynamic_cast<HdPackMemoryCheckConstantCondition*>(condition.get())) {
checkConstraint(operandB <= 0xFF, "[HDPack] Out of range memoryCheckConstant operand");
}
_data->WatchedMemoryAddresses.emplace(operandA);
((HdPackBaseMemoryCondition*)condition.get())->Initialize(operandA, op, operandB, (uint8_t)mask);
} else if(dynamic_cast<HdPackFrameRangeCondition*>(condition.get())) {
checkConstraint(_data->Version >= 101, "[HDPack] This feature requires version 101+ of HD Packs");
checkConstraint(tokens.size() >= 4, "[HDPack] Condition tag should contain at least 4 parameters");
int32_t operandA;
int32_t operandB;
if(_data->Version == 101) {
operandA = HexUtilities::FromHex(tokens[index++]);
operandB = HexUtilities::FromHex(tokens[index++]);
} else {
//Version 102+
operandA = std::stoi(tokens[index++]);
operandB = std::stoi(tokens[index++]);
HdPackConditionOperator op;
string opString = tokens[index++];
if(opString == "==") {
op = HdPackConditionOperator::Equal;
} else if(opString == "!=") {
op = HdPackConditionOperator::NotEqual;
} else if(opString == ">") {
op = HdPackConditionOperator::GreaterThan;
} else if(opString == "<") {
op = HdPackConditionOperator::LowerThan;
} else if(opString == "<=") {
op = HdPackConditionOperator::LowerThanOrEqual;
} else if(opString == ">=") {
op = HdPackConditionOperator::GreaterThanOrEqual;
} else {
checkConstraint(false, "[HDPack] Invalid operator.");
}
uint32_t operandB = HexUtilities::FromHex(tokens[index++]);
uint32_t mask = 0xFF;
if(tokens.size() > 5 && _data->Version >= 103) {
checkConstraint(operandB <= 0xFF, "[HDPack] Out of range memoryCheck mask");
mask = HexUtilities::FromHex(tokens[index++]);
}
switch(condition->GetConditionType()) {
case HdPackConditionType::MemoryCheck:
if(usePpuMemory) {
checkConstraint(operandB <= 0x3FFF, "[HDPack] Out of range memoryCheck operand");
operandB |= HdPackBaseMemoryCondition::PpuMemoryMarker;
} else {
checkConstraint(operandB <= 0xFFFF, "[HDPack] Out of range memoryCheck operand");
}
_data->WatchedMemoryAddresses.emplace(operandB);
break;
case HdPackConditionType::MemoryCheckConstant:
checkConstraint(operandB <= 0xFF, "[HDPack] Out of range memoryCheckConstant operand");
break;
}
_data->WatchedMemoryAddresses.emplace(operandA);
((HdPackBaseMemoryCondition*)condition.get())->Initialize(operandA, op, operandB, (uint8_t)mask);
break;
}
checkConstraint(operandA >= 0 && operandA <= 0xFFFF, "[HDPack] Out of range frameRange operand");
checkConstraint(operandB >= 0 && operandB <= 0xFFFF, "[HDPack] Out of range frameRange operand");
case HdPackConditionType::FrameRange: {
checkConstraint(_data->Version >= 101, "[HDPack] This feature requires version 101+ of HD Packs");
checkConstraint(tokens.size() >= 4, "[HDPack] Condition tag should contain at least 4 parameters");
((HdPackFrameRangeCondition*)condition.get())->Initialize(operandA, operandB);
int32_t operandA;
int32_t operandB;
if(_data->Version == 101) {
operandA = HexUtilities::FromHex(tokens[index++]);
operandB = HexUtilities::FromHex(tokens[index++]);
} else {
//Version 102+
operandA = std::stoi(tokens[index++]);
operandB = std::stoi(tokens[index++]);
}
checkConstraint(operandA >= 0 && operandA <= 0xFFFF, "[HDPack] Out of range frameRange operand");
checkConstraint(operandB >= 0 && operandB <= 0xFFFF, "[HDPack] Out of range frameRange operand");
((HdPackFrameRangeCondition*)condition.get())->Initialize(operandA, operandB);
break;
}
}
HdPackCondition *cond = condition.get();
@ -549,19 +585,21 @@ void HdPackLoader::ProcessBackgroundTag(vector<string> &tokens, vector<HdPackCon
backgroundInfo.Priority = 10;
backgroundInfo.Left = 0;
backgroundInfo.Top = 0;
backgroundInfo.Conditions.reserve(conditions.size());
for(HdPackCondition* condition : conditions) {
if(
!dynamic_cast<HdPackTileAtPositionCondition*>(condition) &&
!dynamic_cast<HdPackSpriteAtPositionCondition*>(condition) &&
!dynamic_cast<HdPackMemoryCheckCondition*>(condition) &&
!dynamic_cast<HdPackMemoryCheckConstantCondition*>(condition) &&
!dynamic_cast<HdPackFrameRangeCondition*>(condition)
) {
MessageManager::Log("[HDPack] Invalid condition type for background: " + tokens[0]);
return;
} else {
backgroundInfo.Conditions.push_back(condition);
switch(condition->GetConditionType()) {
case HdPackConditionType::TileAtPos:
case HdPackConditionType::SpriteAtPos:
case HdPackConditionType::MemoryCheck:
case HdPackConditionType::MemoryCheckConstant:
case HdPackConditionType::FrameRange:
backgroundInfo.Conditions.push_back(condition);
break;
default:
MessageManager::Log("[HDPack] Invalid condition type for background: " + tokens[0]);
return;
}
}
@ -644,20 +682,33 @@ void HdPackLoader::ProcessSfxTag(vector<string> &tokens)
vector<HdPackCondition*> HdPackLoader::ParseConditionString(string conditionString)
{
vector<string> conditionNames = StringUtilities::Split(conditionString, '&');
FastString conditionName;
vector<HdPackCondition*> conditions;
for(string conditionName : conditionNames) {
conditionName.erase(conditionName.find_last_not_of(" \n\r\t") + 1);
conditions.reserve(3);
auto result = _conditionsByName.find(conditionName);
auto processCondition = [&] {
auto result = _conditionsByName.find(conditionName.ToString());
if(result != _conditionsByName.end()) {
conditions.push_back(result->second);
} else {
MessageManager::Log("[HDPack] Condition not found: " + conditionName);
} else {
MessageManager::Log("[HDPack] Condition not found: " + string(conditionName.ToString()));
}
conditionName.Reset();
};
for(size_t i = 0, len = conditionString.size(); i < len; i++) {
char c = conditionString[i];
if(c == ' ' || c == '\n' || c == '\r' || c == '\t') {
continue;
} else if(c == '&') {
processCondition();
} else {
conditionName.WriteSafe(c);
}
}
processCondition();
return conditions;
}

View file

@ -15,6 +15,13 @@ public:
FastString(const char* str, int size) { Write(str, size); }
FastString(string &str) { Write(str); }
void WriteSafe(char c)
{
if(_pos < 999) {
_buffer[_pos++] = c;
}
}
void Write(char c)
{
if(_lowerCase) {
@ -77,6 +84,11 @@ public:
return _pos;
}
void Reset()
{
_pos = 0;
}
template<typename T, typename... Args>
void WriteAll(T first, Args... args)
{

View file

@ -3,12 +3,28 @@
#include <codecvt>
#include <locale>
#ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#undef WIN32_LEAN_AND_MEAN
#endif
namespace utf8
{
std::wstring utf8::decode(const std::string &str)
{
#ifdef _MSC_VER
std::wstring ret;
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), NULL, 0);
if(len > 0) {
ret.resize(len);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), &ret[0], len);
}
return ret;
#else
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
return conv.from_bytes(str);
#endif
}
std::string utf8::encode(const std::wstring &wstr)