Zip: Fixed issues with non-UTF8 filenames in zip files

This commit is contained in:
Souryo 2017-09-30 14:07:07 -04:00
parent ef63dfa816
commit 0d1e9bbcb3
20 changed files with 334 additions and 212 deletions

View file

@ -18,7 +18,7 @@
#include "../Utilities/Timer.h"
#include "../Utilities/FolderUtilities.h"
#include "../Utilities/PlatformUtilities.h"
#include "../Utilities/VirtualFile.h"
#include "VirtualFile.h"
#include "HdBuilderPpu.h"
#include "HdPpu.h"
#include "NsfPpu.h"

View file

@ -3,7 +3,7 @@
#include "stdafx.h"
#include <atomic>
#include "../Utilities/SimpleLock.h"
#include "../Utilities/VirtualFile.h"
#include "VirtualFile.h"
#include "RomData.h"
class Debugger;

View file

@ -758,6 +758,7 @@
<ClInclude Include="UnRom_94.h" />
<ClInclude Include="VideoDecoder.h" />
<ClInclude Include="BaseVideoFilter.h" />
<ClInclude Include="VirtualFile.h" />
<ClInclude Include="VRC1.h" />
<ClInclude Include="VRC2_4.h" />
<ClInclude Include="VRC3.h" />
@ -875,6 +876,7 @@
<ClCompile Include="VideoRenderer.cpp" />
<ClCompile Include="VideoDecoder.cpp" />
<ClCompile Include="BaseVideoFilter.cpp" />
<ClCompile Include="VirtualFile.cpp" />
<ClCompile Include="VsControlManager.cpp" />
<ClCompile Include="ScaleFilter.cpp" />
<ClCompile Include="VsZapper.cpp" />

View file

@ -1240,6 +1240,9 @@
<ClInclude Include="LuaScriptingContext.h">
<Filter>Debugger\Scripting\Lua</Filter>
</ClInclude>
<ClInclude Include="VirtualFile.h">
<Filter>Nes\RomLoader</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
@ -1503,5 +1506,8 @@
<ClCompile Include="HdAudioDevice.cpp">
<Filter>HdPacks</Filter>
</ClCompile>
<ClCompile Include="VirtualFile.cpp">
<Filter>Misc</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -1,6 +1,6 @@
#include <algorithm>
#include "stdafx.h"
#include "../Utilities/VirtualFile.h"
#include "VirtualFile.h"
#include "HdPackBuilder.h"
HdPackBuilder* HdPackBuilder::_instance = nullptr;

View file

@ -1,8 +1,8 @@
#pragma once
#include "stdafx.h"
#include "HdData.h"
#include "../Utilities/ZipReader.h"
#include "../Utilities/VirtualFile.h"
#include "HdData.h"
#include "VirtualFile.h"
class HdPackLoader
{

View file

@ -2,7 +2,7 @@
#include "stdafx.h"
#include "../Utilities/stb_vorbis.h"
#include "../Utilities/blip_buf.h"
#include "../Utilities/VirtualFile.h"
#include "VirtualFile.h"
#include "EmulationSettings.h"
class OggReader

View file

@ -1,10 +1,11 @@
#include "stdafx.h"
#include <algorithm>
#include <unordered_set>
#include "../Utilities/VirtualFile.h"
#include "../Utilities/FolderUtilities.h"
#include "../Utilities/CRC32.h"
#include "../Utilities/sha1.h"
#include "../Utilities/ArchiveReader.h"
#include "VirtualFile.h"
#include "RomLoader.h"
#include "iNesLoader.h"
#include "FdsLoader.h"
@ -98,7 +99,7 @@ string RomLoader::FindMatchingRomInFile(string filePath, HashInfo hashInfo)
{
shared_ptr<ArchiveReader> reader = ArchiveReader::GetReader(filePath);
if(reader) {
for(string file : reader->GetFileList({ ".nes", ".fds", "*.unif", "*.unif", "*.nsf", "*.nsfe" })) {
for(string file : reader->GetFileList(VirtualFile::RomExtensions)) {
RomLoader loader;
vector<uint8_t> fileData;
if(loader.LoadFile(filePath)) {

View file

@ -1,21 +1,21 @@
#pragma once
#include "stdafx.h"
#include "../Utilities/VirtualFile.h"
#include "VirtualFile.h"
#include "RomData.h"
class ArchiveReader;
class RomLoader
{
private:
RomData _romData;
string _filename;
private:
RomData _romData;
string _filename;
static string FindMatchingRomInFile(string filePath, HashInfo hashInfo);
static string FindMatchingRomInFile(string filePath, HashInfo hashInfo);
public:
bool LoadFile(VirtualFile romFile);
bool LoadFile(string filename, vector<uint8_t> &fileData);
public:
bool LoadFile(VirtualFile romFile);
bool LoadFile(string filename, vector<uint8_t> &fileData);
RomData GetRomData();
static string FindMatchingRom(vector<string> romFiles, string romFilename, HashInfo hashInfo, bool useFastSearch);
RomData GetRomData();
static string FindMatchingRom(vector<string> romFiles, string romFilename, HashInfo hashInfo, bool useFastSearch);
};

View file

@ -215,9 +215,10 @@ void SaveStateManager::LoadRecentGame(string filename, bool resetGame)
Console::Pause();
try {
Console::LoadROM(romPath, patchPath);
if(!resetGame) {
SaveStateManager::LoadState(stateStream, false);
if(Console::LoadROM(romPath, patchPath)) {
if(!resetGame) {
SaveStateManager::LoadState(stateStream, false);
}
}
} catch(std::exception ex) {
Console::GetInstance()->Stop();

160
Core/VirtualFile.cpp Normal file
View file

@ -0,0 +1,160 @@
#include "stdafx.h"
#include <algorithm>
#include <iterator>
#include "VirtualFile.h"
#include "../Utilities/sha1.h"
#include "../Utilities/ArchiveReader.h"
#include "../Utilities/StringUtilities.h"
#include "../Utilities/FolderUtilities.h"
#include "../Utilities/BpsPatcher.h"
#include "../Utilities/IpsPatcher.h"
#include "../Utilities/UpsPatcher.h"
const std::initializer_list<string> VirtualFile::RomExtensions = { ".nes", ".fds", ".nsf", ".nsfe", "*.unf", "*.unif" };
VirtualFile::VirtualFile()
{
}
VirtualFile::VirtualFile(const string & archivePath, const string innerFile)
{
_path = archivePath;
_innerFile = innerFile;
}
VirtualFile::VirtualFile(const string & file)
{
vector<string> tokens = StringUtilities::Split(file, '\x1');
_path = tokens[0];
if(tokens.size() > 1) {
_innerFile = tokens[1];
if(tokens.size() > 2) {
try {
_innerFileIndex = std::stoi(tokens[2]);
} catch(std::exception) {}
}
}
}
VirtualFile::VirtualFile(std::istream & input, string filePath)
{
_path = filePath;
FromStream(input, _data);
}
VirtualFile::operator std::string() const
{
if(_innerFile.empty()) {
return _path;
} else if(_path.empty()) {
throw std::runtime_error("Cannot convert to string");
} else {
return _path + "\x1" + _innerFile;
}
}
void VirtualFile::FromStream(std::istream & input, vector<uint8_t>& output)
{
input.seekg(0, std::ios::end);
uint32_t fileSize = (uint32_t)input.tellg();
input.seekg(0, std::ios::beg);
output.resize(fileSize, 0);
input.read((char*)output.data(), fileSize);
}
void VirtualFile::LoadFile()
{
if(_data.size() == 0) {
if(!_innerFile.empty()) {
shared_ptr<ArchiveReader> reader = ArchiveReader::GetReader(_path);
if(reader) {
if(_innerFileIndex >= 0) {
vector<string> filelist = reader->GetFileList(VirtualFile::RomExtensions);
if(filelist.size() > _innerFileIndex) {
reader->ExtractFile(filelist[_innerFileIndex], _data);
}
} else {
reader->ExtractFile(_innerFile, _data);
}
}
} else {
ifstream input(_path, std::ios::in | std::ios::binary);
if(input.good()) {
FromStream(input, _data);
}
}
}
}
bool VirtualFile::IsValid()
{
LoadFile();
return _data.size() > 0;
}
string VirtualFile::GetFilePath()
{
return _path;
}
string VirtualFile::GetFolderPath()
{
return FolderUtilities::GetFolderName(_path);
}
string VirtualFile::GetFileName()
{
return _innerFile.empty() ? FolderUtilities::GetFilename(_path, true) : _innerFile;
}
string VirtualFile::GetSha1Hash()
{
string hash = SHA1::GetHash(_data);
std::transform(hash.begin(), hash.end(), hash.begin(), ::tolower);
return hash;
}
bool VirtualFile::ReadFile(vector<uint8_t>& out)
{
LoadFile();
if(_data.size() > 0) {
out.resize(_data.size(), 0);
std::copy(_data.begin(), _data.end(), out.begin());
return true;
}
return false;
}
bool VirtualFile::ReadFile(std::stringstream & out)
{
LoadFile();
if(_data.size() > 0) {
out.write((char*)_data.data(), _data.size());
return true;
}
return false;
}
bool VirtualFile::ApplyPatch(VirtualFile & patch)
{
//Apply patch file
bool result = false;
if(patch.IsValid() && patch._data.size() >= 5) {
vector<uint8_t> patchedData;
std::stringstream ss;
patch.ReadFile(ss);
if(memcmp(patch._data.data(), "PATCH", 5) == 0) {
result = IpsPatcher::PatchBuffer(ss, _data, patchedData);
} else if(memcmp(patch._data.data(), "UPS1", 4) == 0) {
result = UpsPatcher::PatchBuffer(ss, _data, patchedData);
} else if(memcmp(patch._data.data(), "BPS1", 4) == 0) {
result = BpsPatcher::PatchBuffer(ss, _data, patchedData);
}
if(result) {
_data = patchedData;
}
}
return result;
}

37
Core/VirtualFile.h Normal file
View file

@ -0,0 +1,37 @@
#pragma once
#include "stdafx.h"
#include <sstream>
class VirtualFile
{
private:
string _path = "";
string _innerFile = "";
int32_t _innerFileIndex = -1;
vector<uint8_t> _data;
void FromStream(std::istream &input, vector<uint8_t> &output);
void LoadFile();
public:
static const std::initializer_list<string> RomExtensions;
VirtualFile();
VirtualFile(const string &archivePath, const string innerFile);
VirtualFile(const string &file);
VirtualFile(std::istream &input, string filePath);
operator std::string() const;
bool IsValid();
string GetFilePath();
string GetFolderPath();
string GetFileName();
string GetSha1Hash();
bool ReadFile(vector<uint8_t> &out);
bool ReadFile(std::stringstream &out);
bool ApplyPatch(VirtualFile &patch);
};

View file

@ -11,6 +11,7 @@ namespace Mesen.GUI.Forms
{
public string Path { get; set; }
public string InnerFile { get; set; }
public int InnerFileIndex { get; set; }
public bool Exists { get { return File.Exists(Path); } }
public bool Compressed { get { return !string.IsNullOrWhiteSpace(InnerFile); } }
@ -20,7 +21,14 @@ namespace Mesen.GUI.Forms
public override string ToString()
{
return Path + (Compressed ? ("\x1" + InnerFile) : "");
string resPath = Path;
if(Compressed) {
resPath += "\x1" + InnerFile;
if(InnerFileIndex > 0) {
resPath += "\x1" + (InnerFileIndex - 1).ToString();
}
}
return resPath;
}
static public implicit operator ResourcePath(string path)
@ -28,7 +36,8 @@ namespace Mesen.GUI.Forms
string[] tokens = path.Split('\x1');
return new ResourcePath() {
Path = tokens[0],
InnerFile = tokens.Length > 1 ? tokens[1] : ""
InnerFile = tokens.Length > 1 ? tokens[1] : "",
InnerFileIndex = tokens.Length > 2 ? (int.Parse(tokens[2]) + 1) : 0
};
}

View file

@ -75,7 +75,6 @@
this.lstRoms.Size = new System.Drawing.Size(457, 182);
this.lstRoms.TabIndex = 1;
this.lstRoms.SelectedIndexChanged += new System.EventHandler(this.lstRoms_SelectedIndexChanged);
this.lstRoms.DoubleClick += new System.EventHandler(this.lstRoms_DoubleClick);
//
// flowLayoutPanel1
//
@ -99,7 +98,6 @@
this.btnOK.TabIndex = 0;
this.btnOK.Text = "OK";
this.btnOK.UseVisualStyleBackColor = true;
this.btnOK.Click += new System.EventHandler(this.btnOK_Click);
//
// btnCancel
//

View file

@ -8,16 +8,17 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static Mesen.GUI.InteropEmu;
namespace Mesen.GUI.Forms
{
public partial class frmSelectRom : BaseForm
{
private List<string> _romFiles;
private List<ArchiveRomEntry> _romFiles;
private int SelectedIndex { get; set; }
private string _previousSearch = "";
public frmSelectRom(List<string> romFiles)
public frmSelectRom(List<ArchiveRomEntry> romFiles)
{
InitializeComponent();
@ -56,9 +57,9 @@ namespace Mesen.GUI.Forms
if(string.IsNullOrWhiteSpace(_previousSearch)) {
lstRoms.Items.AddRange(_romFiles.ToArray());
} else {
List<string> romsToAdd = new List<string>();
foreach(string rom in _romFiles) {
if(rom.IndexOf(_previousSearch, StringComparison.InvariantCultureIgnoreCase) >= 0) {
List<ArchiveRomEntry> romsToAdd = new List<ArchiveRomEntry>();
foreach(ArchiveRomEntry rom in _romFiles) {
if(rom.Filename.IndexOf(_previousSearch, StringComparison.InvariantCultureIgnoreCase) >= 0) {
romsToAdd.Add(rom);
}
}
@ -75,21 +76,28 @@ namespace Mesen.GUI.Forms
public static bool SelectRom(ref ResourcePath resource)
{
List<string> archiveRomList = InteropEmu.GetArchiveRomList(resource.Path);
List<ArchiveRomEntry> archiveRomList = InteropEmu.GetArchiveRomList(resource.Path);
if(archiveRomList.Contains(resource.InnerFile)) {
if(archiveRomList.Select(entry => entry.Filename).Contains(resource.InnerFile)) {
return true;
}
if(archiveRomList.Count > 1) {
frmSelectRom frm = new frmSelectRom(archiveRomList);
if(frm.ShowDialog(null, Application.OpenForms[0]) == DialogResult.OK) {
resource.InnerFile = frm.lstRoms.SelectedItem.ToString();
ArchiveRomEntry entry = frm.lstRoms.SelectedItem as ArchiveRomEntry;
resource.InnerFile = entry.Filename;
if(!entry.IsUtf8) {
resource.InnerFileIndex = archiveRomList.IndexOf(entry) + 1;
}
} else {
return false;
}
} else if(archiveRomList.Count == 1) {
resource.InnerFile = archiveRomList[0];
resource.InnerFile = archiveRomList[0].Filename;
if(!archiveRomList[0].IsUtf8) {
resource.InnerFileIndex = 1;
}
} else {
resource.InnerFile = "";
}
@ -97,27 +105,11 @@ namespace Mesen.GUI.Forms
return true;
}
void SetSelectedIndex()
{
this.SelectedIndex = _romFiles.IndexOf(lstRoms.SelectedItem.ToString());
this.DialogResult = DialogResult.OK;
}
private void lstRoms_SelectedIndexChanged(object sender, EventArgs e)
{
btnOK.Enabled = lstRoms.SelectedItems.Count > 0;
}
private void lstRoms_DoubleClick(object sender, EventArgs e)
{
SetSelectedIndex();
}
private void btnOK_Click(object sender, EventArgs e)
{
SetSelectedIndex();
}
private void tmrSearch_Tick(object sender, EventArgs e)
{
if(txtSearch.Text.Trim() != _previousSearch) {

View file

@ -32,7 +32,6 @@ namespace Mesen.GUI
[DllImport(DLLPath)] public static extern void LoadRecentGame([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filepath, [MarshalAs(UnmanagedType.I1)]bool resetGame);
[DllImport(DLLPath, EntryPoint = "GetArchiveRomList")] private static extern IntPtr GetArchiveRomListWrapper([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filename);
public static List<string> GetArchiveRomList(string filename) { return new List<string>(PtrToStringUtf8(InteropEmu.GetArchiveRomListWrapper(filename)).Split(new string[] { "[!|!]" }, StringSplitOptions.RemoveEmptyEntries)); }
[DllImport(DLLPath)] public static extern void SetMousePosition(double x, double y);
[DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool HasZapper();
@ -671,6 +670,65 @@ namespace Mesen.GUI
return new List<string>(PtrToStringUtf8(InteropEmu.GetAudioDevicesWrapper()).Split(new string[1] { "||" }, StringSplitOptions.RemoveEmptyEntries));
}
public class ArchiveRomEntry
{
public string Filename;
public bool IsUtf8;
public override string ToString()
{
return Filename;
}
}
public static List<ArchiveRomEntry> GetArchiveRomList(string filename)
{
//Split the array on the [!|!] delimiter
byte[] buffer = PtrToByteArray(InteropEmu.GetArchiveRomListWrapper(filename));
List<List<byte>> filenames = new List<List<byte>>();
List<byte> filenameBytes = new List<byte>();
for(int i = 0; i < buffer.Length - 5; i++) {
if(buffer[i] == '[' && buffer[i+1] == '!' && buffer[i+2] == '|' && buffer[i+3] == '!' && buffer[i+4] == ']') {
filenames.Add(filenameBytes);
filenameBytes = new List<byte>();
i+=4;
} else {
filenameBytes.Add(buffer[i]);
}
}
filenames.Add(filenameBytes);
List<ArchiveRomEntry> entries = new List<ArchiveRomEntry>();
//Check whether or not each string is a valid utf8 filename, if not decode it using the system's default encoding.
//This is necessary because zip files do not have any rules when it comes to encoding filenames
for(int i = 0; i < filenames.Count; i++) {
byte[] originalBytes = filenames[i].ToArray();
string utf8Filename = Encoding.UTF8.GetString(originalBytes);
byte[] convertedBytes = Encoding.UTF8.GetBytes(utf8Filename);
bool equal = true;
if(originalBytes.Length == convertedBytes.Length) {
for(int j = 0; j < convertedBytes.Length; j++) {
if(convertedBytes[j] != originalBytes[j]) {
equal = false;
break;
}
}
} else {
equal = false;
}
if(!equal) {
//String doesn't appear to be an utf8 string, use the system's default encoding
entries.Add(new ArchiveRomEntry() { Filename = Encoding.Default.GetString(originalBytes), IsUtf8 = false });
} else {
entries.Add(new ArchiveRomEntry() { Filename = utf8Filename, IsUtf8 = true });
}
}
return entries;
}
private static byte[] _codeByteArray = new byte[0];
private static string PtrToStringUtf8(IntPtr ptr, UInt32 length = 0)
{
@ -705,6 +763,23 @@ namespace Mesen.GUI
}
}
private static byte[] PtrToByteArray(IntPtr ptr)
{
if(ptr == IntPtr.Zero) {
return new byte[0];
}
int len = 0;
while(System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) {
len++;
}
byte[] array = new byte[len];
System.Runtime.InteropServices.Marshal.Copy(ptr, array, 0, len);
return array;
}
public enum ConsoleNotificationType
{
GameLoaded = 0,

View file

@ -20,6 +20,7 @@
#include "../Core/IRenderingDevice.h"
#include "../Core/IAudioDevice.h"
#include "../Core/MovieManager.h"
#include "../Core/VirtualFile.h"
#include "../Core/HdPackBuilder.h"
#include "../Utilities/AviWriter.h"
#include "../Core/ShortcutKeyHandler.h"
@ -128,7 +129,7 @@ namespace InteropEmu {
std::ostringstream out;
shared_ptr<ArchiveReader> reader = ArchiveReader::GetReader(filename);
if(reader) {
for(string romName : reader->GetFileList({ ".nes", ".fds", ".nsf", ".nsfe", "*.unf" })) {
for(string romName : reader->GetFileList(VirtualFile::RomExtensions)) {
out << romName << "[!|!]";
}
}

View file

@ -360,7 +360,6 @@
<ClInclude Include="Timer.h" />
<ClInclude Include="UpsPatcher.h" />
<ClInclude Include="UTF8Util.h" />
<ClInclude Include="VirtualFile.h" />
<ClInclude Include="xBRZ\config.h" />
<ClInclude Include="xBRZ\xbrz.h" />
<ClInclude Include="ZipReader.h" />

View file

@ -158,9 +158,6 @@
<ClInclude Include="ArchiveReader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="VirtualFile.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="stb_vorbis.h">
<Filter>Header Files</Filter>
</ClInclude>

View file

@ -1,156 +0,0 @@
#pragma once
#include "stdafx.h"
#include <algorithm>
#include <iterator>
#include <sstream>
#include "sha1.h"
#include "ArchiveReader.h"
#include "StringUtilities.h"
#include "FolderUtilities.h"
#include "BpsPatcher.h"
#include "IpsPatcher.h"
#include "UpsPatcher.h"
class VirtualFile
{
private:
string _path = "";
string _innerFile = "";
vector<uint8_t> _data;
void FromStream(std::istream &input, vector<uint8_t> &output)
{
input.seekg(0, std::ios::end);
uint32_t fileSize = (uint32_t)input.tellg();
input.seekg(0, std::ios::beg);
output.resize(fileSize, 0);
input.read((char*)output.data(), fileSize);
}
void LoadFile()
{
if(_data.size() == 0) {
if(!_innerFile.empty()) {
shared_ptr<ArchiveReader> reader = ArchiveReader::GetReader(_path);
if(reader) {
reader->ExtractFile(_innerFile, _data);
}
} else {
ifstream input(_path, std::ios::in | std::ios::binary);
if(input.good()) {
FromStream(input, _data);
}
}
}
}
public:
VirtualFile()
{
}
VirtualFile(const string &archivePath, const string innerFile)
{
_path = archivePath;
_innerFile = innerFile;
}
VirtualFile(const string &file)
{
vector<string> tokens = StringUtilities::Split(file, '\x1');
_path = tokens[0];
if(tokens.size() > 1) {
_innerFile = tokens[1];
}
}
VirtualFile(std::istream &input, string filePath)
{
_path = filePath;
FromStream(input, _data);
}
operator std::string() const
{
if(_innerFile.empty()) {
return _path;
} else if(_path.empty()) {
throw std::runtime_error("Cannot convert to string");
} else {
return _path + "\x1" + _innerFile;
}
}
bool IsValid()
{
LoadFile();
return _data.size() > 0;
}
string GetFilePath()
{
return _path;
}
string GetFolderPath()
{
return FolderUtilities::GetFolderName(_path);
}
string GetFileName()
{
return _innerFile.empty() ? FolderUtilities::GetFilename(_path, true) : _innerFile;
}
string GetSha1Hash()
{
string hash = SHA1::GetHash(_data);
std::transform(hash.begin(), hash.end(), hash.begin(), ::tolower);
return hash;
}
bool ReadFile(vector<uint8_t> &out)
{
LoadFile();
if(_data.size() > 0) {
out.resize(_data.size(), 0);
std::copy(_data.begin(), _data.end(), out.begin());
return true;
}
return false;
}
bool ReadFile(std::stringstream &out)
{
LoadFile();
if(_data.size() > 0) {
out.write((char*)_data.data(), _data.size());
return true;
}
return false;
}
bool ApplyPatch(VirtualFile &patch)
{
//Apply patch file
bool result = false;
if(patch.IsValid() && patch._data.size() >= 5 ) {
vector<uint8_t> patchedData;
std::stringstream ss;
patch.ReadFile(ss);
if(memcmp(patch._data.data(), "PATCH", 5) == 0) {
result = IpsPatcher::PatchBuffer(ss, _data, patchedData);
} else if(memcmp(patch._data.data(), "UPS1", 4) == 0) {
result = UpsPatcher::PatchBuffer(ss, _data, patchedData);
} else if(memcmp(patch._data.data(), "BPS1", 4) == 0) {
result = BpsPatcher::PatchBuffer(ss, _data, patchedData);
}
if(result) {
_data = patchedData;
}
}
return result;
}
};