Merge branch 'd1str4ught:main' into main
This commit is contained in:
@@ -14,4 +14,5 @@ add_subdirectory(SpeedTreeLib)
|
||||
add_subdirectory(SphereLib)
|
||||
add_subdirectory(UserInterface)
|
||||
add_subdirectory(PackMaker)
|
||||
add_subdirectory(DumpProto)
|
||||
add_subdirectory(PackLib)
|
||||
|
||||
10
src/DumpProto/CMakeLists.txt
Normal file
10
src/DumpProto/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
file(GLOB_RECURSE FILE_SOURCES "*.h" "*.c" "*.cpp")
|
||||
|
||||
add_executable(DumpProto ${FILE_SOURCES})
|
||||
set_target_properties(DumpProto PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||
)
|
||||
|
||||
target_link_libraries(DumpProto
|
||||
lzo2
|
||||
)
|
||||
432
src/DumpProto/CsvFile.cpp
Normal file
432
src/DumpProto/CsvFile.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
#include "CsvFile.h"
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
||||
#ifndef Assert
|
||||
#include <assert.h>
|
||||
#define Assert assert
|
||||
#define LogToFile (void)(0);
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
/// <20>Ľ̿<C4BD> state <20><><EFBFBD>Ű<EFBFBD>
|
||||
enum ParseState
|
||||
{
|
||||
STATE_NORMAL = 0, ///< <20>Ϲ<EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
STATE_QUOTE ///< <20><><EFBFBD><EFBFBD>ǥ <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
};
|
||||
|
||||
/// <20><><EFBFBD>ڿ<EFBFBD> <20>¿<EFBFBD><C2BF><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ؼ<EFBFBD> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
std::string Trim(std::string str)
|
||||
{
|
||||
str = str.erase(str.find_last_not_of(" \t\r\n") + 1);
|
||||
str = str.erase(0, str.find_first_not_of(" \t\r\n"));
|
||||
return str;
|
||||
}
|
||||
|
||||
/// \brief <20>־<EFBFBD><D6BE><EFBFBD> <20><><EFBFBD>忡 <20>ִ<EFBFBD> <20><><EFBFBD>ĺ<EFBFBD><C4BA><EFBFBD> <20><><EFBFBD> <20>ҹ<EFBFBD><D2B9>ڷ<EFBFBD> <20>ٲ۴<D9B2>.
|
||||
std::string Lower(std::string original)
|
||||
{
|
||||
std::transform(original.begin(), original.end(), original.begin(), tolower);
|
||||
return original;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20><><EFBFBD><EFBFBD> <20><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD> <20><>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20≯<EFBFBD><CCB8><EFBFBD> <20><><EFBFBD><EFBFBD>Ѵ<EFBFBD>.
|
||||
/// \param name <20><> <20≯<EFBFBD>
|
||||
/// \param index <20><> <20>ε<EFBFBD><CEB5><EFBFBD>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void cCsvAlias::AddAlias(const char* name, size_t index)
|
||||
{
|
||||
std::string converted(Lower(name));
|
||||
|
||||
Assert(m_Name2Index.find(converted) == m_Name2Index.end());
|
||||
Assert(m_Index2Name.find(index) == m_Index2Name.end());
|
||||
|
||||
m_Name2Index.insert(NAME2INDEX_MAP::value_type(converted, index));
|
||||
m_Index2Name.insert(INDEX2NAME_MAP::value_type(index, name));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ѵ<EFBFBD>.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void cCsvAlias::Destroy()
|
||||
{
|
||||
m_Name2Index.clear();
|
||||
m_Index2Name.clear();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20><><EFBFBD><EFBFBD> <20>ε<EFBFBD><CEB5><EFBFBD><EFBFBD><EFBFBD> <20≯<EFBFBD><CCB8><EFBFBD><EFBFBD><EFBFBD> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
/// \param index <20><><EFBFBD><EFBFBD> <20>ε<EFBFBD><CEB5><EFBFBD>
|
||||
/// \return const char* <20≯<EFBFBD>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
const char* cCsvAlias::operator [] (size_t index) const
|
||||
{
|
||||
INDEX2NAME_MAP::const_iterator itr(m_Index2Name.find(index));
|
||||
if (itr == m_Index2Name.end())
|
||||
{
|
||||
LogToFile(NULL, "cannot find suitable conversion for %d", index);
|
||||
Assert(false && "cannot find suitable conversion");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return itr->second.c_str();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20≯<EFBFBD><CCB8><EFBFBD> <20><><EFBFBD><EFBFBD> <20>ε<EFBFBD><CEB5><EFBFBD><EFBFBD><EFBFBD> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
/// \param name <20≯<EFBFBD>
|
||||
/// \return size_t <20><><EFBFBD><EFBFBD> <20>ε<EFBFBD><CEB5><EFBFBD>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
size_t cCsvAlias::operator [] (const char* name) const
|
||||
{
|
||||
NAME2INDEX_MAP::const_iterator itr(m_Name2Index.find(Lower(name)));
|
||||
if (itr == m_Name2Index.end())
|
||||
{
|
||||
LogToFile(NULL, "cannot find suitable conversion for %s", name);
|
||||
Assert(false && "cannot find suitable conversion");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20≯<EFBFBD><CCB8><EFBFBD> CSV <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ε<EFBFBD><CEB5>Ѵ<EFBFBD>.
|
||||
/// \param fileName CSV <20><><EFBFBD><EFBFBD> <20≯<EFBFBD>
|
||||
/// \param seperator <20>ʵ<EFBFBD> <20>и<EFBFBD><D0B8>ڷ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>. <20>⺻<EFBFBD><E2BABB><EFBFBD><EFBFBD> ','<27>̴<EFBFBD>.
|
||||
/// \param quote <20><><EFBFBD><EFBFBD>ǥ<EFBFBD><C7A5> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>. <20>⺻<EFBFBD><E2BABB><EFBFBD><EFBFBD> '"'<27>̴<EFBFBD>.
|
||||
/// \return bool <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ε<EFBFBD><CEB5>ߴٸ<DFB4> true, <20>ƴ϶<C6B4><CFB6> false
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool cCsvFile::Load(const char* fileName, const char seperator, const char quote)
|
||||
{
|
||||
Assert(seperator != quote);
|
||||
|
||||
// Open file in binary mode to preserve UTF-8 encoding
|
||||
std::ifstream file(fileName, std::ios::in | std::ios::binary);
|
||||
if (!file) return false;
|
||||
|
||||
Destroy(); // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
|
||||
cCsvRow* row = NULL;
|
||||
ParseState state = STATE_NORMAL;
|
||||
std::string token = "";
|
||||
char buf[2048+1] = {0,};
|
||||
|
||||
while (file.good())
|
||||
{
|
||||
file.getline(buf, 2048);
|
||||
buf[sizeof(buf)-1] = 0;
|
||||
|
||||
std::string line(Trim(buf));
|
||||
if (line.empty() || (state == STATE_NORMAL && line[0] == '#')) continue;
|
||||
|
||||
std::string text = std::string(line) + " "; // <20>Ľ<EFBFBD> lookahead <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ٿ<EFBFBD><D9BF>ش<EFBFBD>.
|
||||
size_t cur = 0;
|
||||
|
||||
while (cur < text.size())
|
||||
{
|
||||
// <20><><EFBFBD><EFBFBD> <20><>尡 QUOTE <20><><EFBFBD><EFBFBD><EFBFBD> <20><>,
|
||||
if (state == STATE_QUOTE)
|
||||
{
|
||||
// '"' <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD>̴<EFBFBD>.
|
||||
// 1. <20><> <20><><EFBFBD>ο<EFBFBD> Ư<><C6AF> <20><><EFBFBD>ڰ<EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD> <20≯<EFBFBD> <20>˸<EFBFBD><CBB8><EFBFBD> <20><> <20>¿<EFBFBD><C2BF><EFBFBD> <20><>
|
||||
// 2. <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> '"' <20><><EFBFBD>ڰ<EFBFBD> '"' 2<><32><EFBFBD><EFBFBD> ġȯ<C4A1><C8AF> <20><>
|
||||
// <20><> <20><> ù<><C3B9>° <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ִ<EFBFBD> <20><><EFBFBD><EFBFBD> CSV <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̶<EFBFBD><CCB6>,
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> STATE_NORMAL<41><4C> <20>ɸ<EFBFBD><C9B8><EFBFBD> <20>Ǿ<EFBFBD><C7BE>ִ<EFBFBD>.
|
||||
// <20><EFBFBD><D7B7>Ƿ<EFBFBD> <20><><EFBFBD>⼭ <20>ɸ<EFBFBD><C9B8><EFBFBD> <20><><EFBFBD><EFBFBD> 1<><31><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>쳪, 2<><32> <20><><EFBFBD> <20><><EFBFBD>̴<EFBFBD>.
|
||||
// 2<><32><EFBFBD><EFBFBD> <20><>쿡<EFBFBD><ECBFA1> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> '"' <20><><EFBFBD>ڰ<EFBFBD> 2<><32><EFBFBD><EFBFBD> <20><>Ÿ<EFBFBD><C5B8><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1<><31><EFBFBD><EFBFBD>
|
||||
// <20><><EFBFBD><EFBFBD> <20><>쿡<EFBFBD><ECBFA1> <20>ƴϴ<C6B4>. <20≯<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ؼ<EFBFBD> <20>ڵ带 ¥<><C2A5>...
|
||||
if (text[cur] == quote)
|
||||
{
|
||||
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD>ڰ<EFBFBD> '"' <20><><EFBFBD>ڶ<EFBFBD><DAB6>, <20><> <20><><EFBFBD>ӵ<EFBFBD> '"' <20><><EFBFBD>ڶ<EFBFBD><DAB6>
|
||||
// <20>̴<EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> '"' <20><><EFBFBD>ڰ<EFBFBD> ġȯ<C4A1><C8AF> <20><><EFBFBD>̴<EFBFBD>.
|
||||
if (text[cur+1] == quote)
|
||||
{
|
||||
token += quote;
|
||||
++cur;
|
||||
}
|
||||
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD>ڰ<EFBFBD> '"' <20><><EFBFBD>ڰ<EFBFBD> <20>ƴ϶<C6B4><CFB6>
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> '"'<27><><EFBFBD>ڴ<EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>˸<EFBFBD><CBB8><EFBFBD> <20><><EFBFBD>ڶ<EFBFBD><DAB6> <20><> <20><> <20>ִ<EFBFBD>.
|
||||
else
|
||||
{
|
||||
state = STATE_NORMAL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
token += text[cur];
|
||||
}
|
||||
}
|
||||
// <20><><EFBFBD><EFBFBD> <20><>尡 NORMAL <20><><EFBFBD><EFBFBD><EFBFBD> <20><>,
|
||||
else if (state == STATE_NORMAL)
|
||||
{
|
||||
if (row == NULL)
|
||||
row = new cCsvRow();
|
||||
|
||||
// ',' <20><><EFBFBD>ڸ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ٸ<EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>ǹ<EFBFBD><C7B9>Ѵ<EFBFBD>.
|
||||
// <20><>ū<EFBFBD><C5AB><EFBFBD>μ<EFBFBD> <20><> <20><><EFBFBD><EFBFBD>Ʈ<EFBFBD><C6AE><EFBFBD>ٰ<EFBFBD> <20><><EFBFBD><EFBFBD>ְ<EFBFBD>, <20><>ū<EFBFBD><C5AB> <20>ʱ<EFBFBD>ȭ<EFBFBD>Ѵ<EFBFBD>.
|
||||
if (text[cur] == seperator)
|
||||
{
|
||||
row->push_back(token);
|
||||
token.clear();
|
||||
}
|
||||
// '"' <20><><EFBFBD>ڸ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ٸ<EFBFBD>, QUOTE <20><><EFBFBD><EFBFBD> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
else if (text[cur] == quote)
|
||||
{
|
||||
state = STATE_QUOTE;
|
||||
}
|
||||
// <20>ٸ<EFBFBD> <20>Ϲ<EFBFBD> <20><><EFBFBD>ڶ<EFBFBD><DAB6> <20><><EFBFBD><EFBFBD> <20><>ū<EFBFBD><C5AB><EFBFBD>ٰ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD>.
|
||||
else
|
||||
{
|
||||
token += text[cur];
|
||||
}
|
||||
}
|
||||
|
||||
++cur;
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> ',' <20><><EFBFBD>ڰ<EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>⼭ <20>߰<EFBFBD><DFB0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ѵ<EFBFBD>.
|
||||
// <20><>, ó<><C3B3><EFBFBD><EFBFBD> <20>Ľ<EFBFBD> lookahead <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>̽<EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>.
|
||||
if (state == STATE_NORMAL)
|
||||
{
|
||||
Assert(row != NULL);
|
||||
row->push_back(token.substr(0, token.size()-2));
|
||||
m_Rows.push_back(row);
|
||||
token.clear();
|
||||
row = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
token = token.substr(0, token.size()-2) + "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ִ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> CSV <20><><EFBFBD>Ͽ<EFBFBD><CFBF><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ѵ<EFBFBD>.
|
||||
/// \param fileName CSV <20><><EFBFBD><EFBFBD> <20≯<EFBFBD>
|
||||
/// \param append true<75><65> <20><><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>Ͽ<EFBFBD><CFBF><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD>. false<73><65> <20><>쿡<EFBFBD><ECBFA1>
|
||||
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϰ<EFBFBD>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>.
|
||||
/// \param seperator <20>ʵ<EFBFBD> <20>и<EFBFBD><D0B8>ڷ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>. <20>⺻<EFBFBD><E2BABB><EFBFBD><EFBFBD> ','<27>̴<EFBFBD>.
|
||||
/// \param quote <20><><EFBFBD><EFBFBD>ǥ<EFBFBD><C7A5> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>. <20>⺻<EFBFBD><E2BABB><EFBFBD><EFBFBD> '"'<27>̴<EFBFBD>.
|
||||
/// \return bool <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ߴٸ<DFB4> true, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>쿡<EFBFBD><ECBFA1> false
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool cCsvFile::Save(const char* fileName, bool append, char seperator, char quote) const
|
||||
{
|
||||
Assert(seperator != quote);
|
||||
|
||||
// <20><><EFBFBD> <20><>忡 <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>÷<EFBFBD><C3B7><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ѵ<EFBFBD>.
|
||||
std::ofstream file;
|
||||
if (append) { file.open(fileName, std::ios::out | std::ios::app); }
|
||||
else { file.open(fileName, std::ios::out | std::ios::trunc); }
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>ߴٸ<DFB4>, false<73><65> <20><><EFBFBD><EFBFBD><EFBFBD>Ѵ<EFBFBD>.
|
||||
if (!file) return false;
|
||||
|
||||
char special_chars[5] = { seperator, quote, '\r', '\n', 0 };
|
||||
char quote_escape_string[3] = { quote, quote, 0 };
|
||||
|
||||
// <20><><EFBFBD> <20><><EFBFBD><EFBFBD> Ⱦ<><C8BE><EFBFBD>ϸ鼭...
|
||||
for (size_t i=0; i<m_Rows.size(); i++)
|
||||
{
|
||||
const cCsvRow& row = *((*this)[i]);
|
||||
|
||||
std::string line;
|
||||
|
||||
// <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD> <20><>ū<EFBFBD><C5AB> Ⱦ<><C8BE><EFBFBD>ϸ鼭...
|
||||
for (size_t j=0; j<row.size(); j++)
|
||||
{
|
||||
const std::string& token = row[j];
|
||||
|
||||
// <20>Ϲ<EFBFBD><CFB9><EFBFBD><EFBFBD><EFBFBD>('"' <20>Ǵ<EFBFBD> ','<27><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>)
|
||||
// <20><>ū<EFBFBD>̶<EFBFBD><CCB6> <20>׳<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϸ<EFBFBD> <20>ȴ<EFBFBD>.
|
||||
if (token.find_first_of(special_chars) == std::string::npos)
|
||||
{
|
||||
line += token;
|
||||
}
|
||||
// Ư<><C6AF><EFBFBD><EFBFBD><EFBFBD>ڸ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>ū<EFBFBD>̶<EFBFBD><CCB6> <20><><EFBFBD>ڿ<EFBFBD> <20>¿쿡 '"'<27><> <20>ٿ<EFBFBD><D9BF>ְ<EFBFBD>,
|
||||
// <20><><EFBFBD>ڿ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> '"'<27><> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ѵ<EFBFBD>.
|
||||
else
|
||||
{
|
||||
line += quote;
|
||||
|
||||
for (size_t k=0; k<token.size(); k++)
|
||||
{
|
||||
if (token[k] == quote) line += quote_escape_string;
|
||||
else line += token[k];
|
||||
}
|
||||
|
||||
line += quote;
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>ƴ϶<C6B4><CFB6> ','<27><> <20><>ū<EFBFBD><C5AB> <20>ڿ<EFBFBD><DABF><EFBFBD> <20>ٿ<EFBFBD><D9BF><EFBFBD><EFBFBD><EFBFBD>Ѵ<EFBFBD>.
|
||||
if (j != row.size() - 1) { line += seperator; }
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>Ѵ<EFBFBD>.
|
||||
file << line << std::endl;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><DEB8><F0B8AEBF><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ѵ<EFBFBD>.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void cCsvFile::Destroy()
|
||||
{
|
||||
for (ROWS::iterator itr(m_Rows.begin()); itr != m_Rows.end(); ++itr)
|
||||
delete *itr;
|
||||
|
||||
m_Rows.clear();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20>ش<EFBFBD><D8B4>ϴ<EFBFBD> <20>ε<EFBFBD><CEB5><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
/// \param index <20>ε<EFBFBD><CEB5><EFBFBD>
|
||||
/// \return cCsvRow* <20>ش<EFBFBD> <20><>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
cCsvRow* cCsvFile::operator [] (size_t index)
|
||||
{
|
||||
Assert(index < m_Rows.size());
|
||||
return m_Rows[index];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20>ش<EFBFBD><D8B4>ϴ<EFBFBD> <20>ε<EFBFBD><CEB5><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
/// \param index <20>ε<EFBFBD><CEB5><EFBFBD>
|
||||
/// \return const cCsvRow* <20>ش<EFBFBD> <20><>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
const cCsvRow* cCsvFile::operator [] (size_t index) const
|
||||
{
|
||||
Assert(index < m_Rows.size());
|
||||
return m_Rows[index];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
cCsvTable::cCsvTable()
|
||||
: m_CurRow(-1)
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20>Ҹ<EFBFBD><D2B8><EFBFBD>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
cCsvTable::~cCsvTable()
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20≯<EFBFBD><CCB8><EFBFBD> CSV <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ε<EFBFBD><CEB5>Ѵ<EFBFBD>.
|
||||
/// \param fileName CSV <20><><EFBFBD><EFBFBD> <20≯<EFBFBD>
|
||||
/// \param seperator <20>ʵ<EFBFBD> <20>и<EFBFBD><D0B8>ڷ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>. <20>⺻<EFBFBD><E2BABB><EFBFBD><EFBFBD> ','<27>̴<EFBFBD>.
|
||||
/// \param quote <20><><EFBFBD><EFBFBD>ǥ<EFBFBD><C7A5> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>. <20>⺻<EFBFBD><E2BABB><EFBFBD><EFBFBD> '"'<27>̴<EFBFBD>.
|
||||
/// \return bool <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ε<EFBFBD><CEB5>ߴٸ<DFB4> true, <20>ƴ϶<C6B4><CFB6> false
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool cCsvTable::Load(const char* fileName, const char seperator, const char quote)
|
||||
{
|
||||
Destroy();
|
||||
return m_File.Load(fileName, seperator, quote);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>Ѿ<D1BE><EEB0A3>.
|
||||
/// \return bool <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>Ѿ <20><><EFBFBD> true<75><65> <20><>ȯ<EFBFBD>ϰ<EFBFBD>, <20><> <20>̻<EFBFBD>
|
||||
/// <20>Ѿ <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ʴ<EFBFBD> <20><>쿡<EFBFBD><ECBFA1> false<73><65> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool cCsvTable::Next()
|
||||
{
|
||||
// 20<32><30><EFBFBD> <20><><EFBFBD><EFBFBD> ȣ<><C8A3><EFBFBD>ϸ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>÷ΰ<C3B7> <20>Ͼ<CFBE>ٵ<EFBFBD>...<2E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?
|
||||
return ++m_CurRow < (int)m_File.GetRowCount() ? true : false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD>ڸ<EFBFBD> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
/// \return size_t <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
size_t cCsvTable::ColCount() const
|
||||
{
|
||||
return CurRow()->size();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20>ε<EFBFBD><CEB5><EFBFBD><EFBFBD><EFBFBD> <20>̿<EFBFBD><CCBF><EFBFBD> int <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
/// \param index <20><> <20>ε<EFBFBD><CEB5><EFBFBD>
|
||||
/// \return int <20><> <20><>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
int cCsvTable::AsInt(size_t index) const
|
||||
{
|
||||
const cCsvRow* const row = CurRow();
|
||||
Assert(row);
|
||||
Assert(index < row->size());
|
||||
return row->AsInt(index);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20>ε<EFBFBD><CEB5><EFBFBD><EFBFBD><EFBFBD> <20>̿<EFBFBD><CCBF><EFBFBD> double <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
/// \param index <20><> <20>ε<EFBFBD><CEB5><EFBFBD>
|
||||
/// \return double <20><> <20><>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
double cCsvTable::AsDouble(size_t index) const
|
||||
{
|
||||
const cCsvRow* const row = CurRow();
|
||||
Assert(row);
|
||||
Assert(index < row->size());
|
||||
return row->AsDouble(index);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20>ε<EFBFBD><CEB5><EFBFBD><EFBFBD><EFBFBD> <20>̿<EFBFBD><CCBF><EFBFBD> std::string <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
/// \param index <20><> <20>ε<EFBFBD><CEB5><EFBFBD>
|
||||
/// \return const char* <20><> <20><>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
const char* cCsvTable::AsStringByIndex(size_t index) const
|
||||
{
|
||||
const cCsvRow* const row = CurRow();
|
||||
Assert(row);
|
||||
Assert(index < row->size());
|
||||
return row->AsString(index);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief alias<61><73> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ѵ<EFBFBD>.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void cCsvTable::Destroy()
|
||||
{
|
||||
m_File.Destroy();
|
||||
m_Alias.Destroy();
|
||||
m_CurRow = -1;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \brief <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
/// \return const cCsvRow* <20><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD> <20><>쿡<EFBFBD><ECBFA1> <20><> <20><><EFBFBD><EFBFBD>
|
||||
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>ȯ<EFBFBD>ϰ<EFBFBD>, <20><> <20>̻<EFBFBD> <20><EFBFBD><D7BC><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>쿡<EFBFBD><ECBFA1> NULL<4C><4C>
|
||||
/// <20><>ȯ<EFBFBD>Ѵ<EFBFBD>.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
const cCsvRow* const cCsvTable::CurRow() const
|
||||
{
|
||||
if (m_CurRow < 0)
|
||||
{
|
||||
Assert(false && "call Next() first!");
|
||||
return NULL;
|
||||
}
|
||||
else if (m_CurRow >= (int)m_File.GetRowCount())
|
||||
{
|
||||
Assert(false && "no more rows!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return m_File[m_CurRow];
|
||||
}
|
||||
|
||||
|
||||
|
||||
325
src/DumpProto/CsvFile.h
Normal file
325
src/DumpProto/CsvFile.h
Normal file
@@ -0,0 +1,325 @@
|
||||
#ifndef __CSVFILE_H__
|
||||
#define __CSVFILE_H__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if _MSC_VER
|
||||
//#include <hash_map>
|
||||
#include <unordered_map>
|
||||
#else
|
||||
#include <map>
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \class cCsvAlias
|
||||
/// \brief CSV 파일을 수정했을 때 발생하는 인덱스 문제를 줄이기 위한
|
||||
/// 별명 객체.
|
||||
///
|
||||
/// 예를 들어 0번 컬럼이 A에 관한 내용을 포함하고, 1번 컬럼이 B에 관한 내용을
|
||||
/// 포함하고 있었는데...
|
||||
///
|
||||
/// <pre>
|
||||
/// int a = row.AsInt(0);
|
||||
/// int b = row.AsInt(1);
|
||||
/// </pre>
|
||||
///
|
||||
/// 그 사이에 C에 관한 내용을 포함하는 컬럼이 끼어든 경우, 하드코딩되어 있는
|
||||
/// 1번을 찾아서 고쳐야 하는데, 상당히 에러가 발생하기 쉬운 작업이다.
|
||||
///
|
||||
/// <pre>
|
||||
/// int a = row.AsInt(0);
|
||||
/// int c = row.AsInt(1);
|
||||
/// int b = row.AsInt(2); <-- 이 부분을 일일이 신경써야 한다.
|
||||
/// </pre>
|
||||
///
|
||||
/// 이 부분을 문자열로 처리하면 유지보수에 들어가는 수고를 약간이나마 줄일 수
|
||||
/// 있다.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class cCsvAlias
|
||||
{
|
||||
private:
|
||||
#if _MSC_VER
|
||||
//typedef stdext::hash_map<std::string, size_t> NAME2INDEX_MAP;
|
||||
//typedef stdext::hash_map<size_t, std::string> INDEX2NAME_MAP;
|
||||
typedef std::unordered_map<std::string, size_t> NAME2INDEX_MAP;
|
||||
typedef std::unordered_map<size_t, std::string> INDEX2NAME_MAP;
|
||||
#else
|
||||
typedef std::map<std::string, size_t> NAME2INDEX_MAP;
|
||||
typedef std::map<size_t, std::string> INDEX2NAME_MAP;
|
||||
#endif
|
||||
|
||||
NAME2INDEX_MAP m_Name2Index; ///< 셀 인덱스 대신으로 사용하기 위한 이름들
|
||||
INDEX2NAME_MAP m_Index2Name; ///< 잘못된 alias를 검사하기 위한 추가적인 맵
|
||||
|
||||
|
||||
public:
|
||||
/// \brief 생성자
|
||||
cCsvAlias() {}
|
||||
|
||||
/// \brief 소멸자
|
||||
virtual ~cCsvAlias() {}
|
||||
|
||||
|
||||
public:
|
||||
/// \brief 셀을 액세스할 때, 숫자 대신 사용할 이름을 등록한다.
|
||||
void AddAlias(const char* name, size_t index);
|
||||
|
||||
/// \brief 모든 데이터를 삭제한다.
|
||||
void Destroy();
|
||||
|
||||
/// \brief 숫자 인덱스를 이름으로 변환한다.
|
||||
const char* operator [] (size_t index) const;
|
||||
|
||||
/// \brief 이름을 숫자 인덱스로 변환한다.
|
||||
size_t operator [] (const char* name) const;
|
||||
|
||||
|
||||
private:
|
||||
/// \brief 복사 생성자 금지
|
||||
cCsvAlias(const cCsvAlias&) {}
|
||||
|
||||
/// \brief 대입 연산자 금지
|
||||
const cCsvAlias& operator = (const cCsvAlias&) { return *this; }
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \class cCsvRow
|
||||
/// \brief CSV 파일의 한 행을 캡슐화한 클래스
|
||||
///
|
||||
/// CSV의 기본 포맷은 엑셀에서 보이는 하나의 셀을 ',' 문자로 구분한 것이다.
|
||||
/// 하지만, 셀 안에 특수 문자로 쓰이는 ',' 문자나 '"' 문자가 들어갈 경우,
|
||||
/// 모양이 약간 이상하게 변한다. 다음은 그 변화의 예이다.
|
||||
///
|
||||
/// <pre>
|
||||
/// 엑셀에서 보이는 모양 | 실제 CSV 파일에 들어가있는 모양
|
||||
/// ---------------------+----------------------------------------------------
|
||||
/// ItemPrice | ItemPrice
|
||||
/// Item,Price | "Item,Price"
|
||||
/// Item"Price | "Item""Price"
|
||||
/// "ItemPrice" | """ItemPrice"""
|
||||
/// "Item,Price" | """Item,Price"""
|
||||
/// Item",Price | "Item"",Price"
|
||||
/// </pre>
|
||||
///
|
||||
/// 이 예로서 다음과 같은 사항을 알 수 있다.
|
||||
/// - 셀 내부에 ',' 또는 '"' 문자가 들어갈 경우, 셀 좌우에 '"' 문자가 생긴다.
|
||||
/// - 셀 내부의 '"' 문자는 2개로 치환된다.
|
||||
///
|
||||
/// \sa cCsvFile
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class cCsvRow : public std::vector<std::string>
|
||||
{
|
||||
public:
|
||||
/// \brief 기본 생성자
|
||||
cCsvRow() {}
|
||||
|
||||
/// \brief 소멸자
|
||||
~cCsvRow() {}
|
||||
|
||||
|
||||
public:
|
||||
/// \brief 해당 셀의 데이터를 int 형으로 반환한다.
|
||||
int AsInt(size_t index) const { return atoi(at(index).c_str()); }
|
||||
|
||||
/// \brief 해당 셀의 데이터를 double 형으로 반환한다.
|
||||
double AsDouble(size_t index) const { return atof(at(index).c_str()); }
|
||||
|
||||
/// \brief 해당 셀의 데이터를 문자열로 반환한다.
|
||||
const char* AsString(size_t index) const { return at(index).c_str(); }
|
||||
|
||||
/// \brief 해당하는 이름의 셀 데이터를 int 형으로 반환한다.
|
||||
int AsInt(const char* name, const cCsvAlias& alias) const {
|
||||
return atoi( at(alias[name]).c_str() );
|
||||
}
|
||||
|
||||
/// \brief 해당하는 이름의 셀 데이터를 int 형으로 반환한다.
|
||||
double AsDouble(const char* name, const cCsvAlias& alias) const {
|
||||
return atof( at(alias[name]).c_str() );
|
||||
}
|
||||
|
||||
/// \brief 해당하는 이름의 셀 데이터를 문자열로 반환한다.
|
||||
const char* AsString(const char* name, const cCsvAlias& alias) const {
|
||||
return at(alias[name]).c_str();
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
/// \brief 복사 생성자 금지
|
||||
cCsvRow(const cCsvRow&) {}
|
||||
|
||||
/// \brief 대입 연산자 금지
|
||||
const cCsvRow& operator = (const cCsvRow&) { return *this; }
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \class cCsvFile
|
||||
/// \brief CSV(Comma Seperated Values) 파일을 read/write하기 위한 클래스
|
||||
///
|
||||
/// <b>sample</b>
|
||||
/// <pre>
|
||||
/// cCsvFile file;
|
||||
///
|
||||
/// cCsvRow row1, row2, row3;
|
||||
/// row1.push_back("ItemPrice");
|
||||
/// row1.push_back("Item,Price");
|
||||
/// row1.push_back("Item\"Price");
|
||||
///
|
||||
/// row2.reserve(3);
|
||||
/// row2[0] = "\"ItemPrice\"";
|
||||
/// row2[1] = "\"Item,Price\"";
|
||||
/// row2[2] = "Item\",Price\"";
|
||||
///
|
||||
/// row3 = "\"ItemPrice\"\"Item,Price\"Item\",Price\"";
|
||||
///
|
||||
/// file.add(row1);
|
||||
/// file.add(row2);
|
||||
/// file.add(row3);
|
||||
/// file.save("test.csv", false);
|
||||
/// </pre>
|
||||
///
|
||||
/// \todo 파일에서만 읽어들일 것이 아니라, 메모리 소스로부터 읽는 함수도
|
||||
/// 있어야 할 듯 하다.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class cCsvFile
|
||||
{
|
||||
private:
|
||||
typedef std::vector<cCsvRow*> ROWS;
|
||||
|
||||
ROWS m_Rows; ///< 행 컬렉션
|
||||
|
||||
|
||||
public:
|
||||
/// \brief 생성자
|
||||
cCsvFile() {}
|
||||
|
||||
/// \brief 소멸자
|
||||
virtual ~cCsvFile() { Destroy(); }
|
||||
|
||||
|
||||
public:
|
||||
/// \brief 지정된 이름의 CSV 파일을 로드한다.
|
||||
bool Load(const char* fileName, const char seperator=',', const char quote='"');
|
||||
|
||||
/// \brief 가지고 있는 내용을 CSV 파일에다 저장한다.
|
||||
bool Save(const char* fileName, bool append=false, char seperator=',', char quote='"') const;
|
||||
|
||||
/// \brief 모든 데이터를 메모리에서 삭제한다.
|
||||
void Destroy();
|
||||
|
||||
/// \brief 해당하는 인덱스의 행을 반환한다.
|
||||
cCsvRow* operator [] (size_t index);
|
||||
|
||||
/// \brief 해당하는 인덱스의 행을 반환한다.
|
||||
const cCsvRow* operator [] (size_t index) const;
|
||||
|
||||
/// \brief 행의 갯수를 반환한다.
|
||||
size_t GetRowCount() const { return m_Rows.size(); }
|
||||
|
||||
|
||||
private:
|
||||
/// \brief 복사 생성자 금지
|
||||
cCsvFile(const cCsvFile&) {}
|
||||
|
||||
/// \brief 대입 연산자 금지
|
||||
const cCsvFile& operator = (const cCsvFile&) { return *this; }
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// \class cCsvTable
|
||||
/// \brief CSV 파일을 이용해 테이블 데이터를 로드하는 경우가 많은데, 이 클래스는
|
||||
/// 그 작업을 좀 더 쉽게 하기 위해 만든 유틸리티 클래스다.
|
||||
///
|
||||
/// CSV 파일을 로드하는 경우, 숫자를 이용해 셀을 액세스해야 하는데, CSV
|
||||
/// 파일의 포맷이 바뀌는 경우, 이 숫자들을 변경해줘야한다. 이 작업이 꽤
|
||||
/// 신경 집중을 요구하는 데다가, 에러가 발생하기 쉽다. 그러므로 숫자로
|
||||
/// 액세스하기보다는 문자열로 액세스하는 것이 약간 느리지만 낫다고 할 수 있다.
|
||||
///
|
||||
/// <b>sample</b>
|
||||
/// <pre>
|
||||
/// cCsvTable table;
|
||||
///
|
||||
/// table.alias(0, "ItemClass");
|
||||
/// table.alias(1, "ItemType");
|
||||
///
|
||||
/// if (table.load("test.csv"))
|
||||
/// {
|
||||
/// while (table.next())
|
||||
/// {
|
||||
/// std::string item_class = table.AsString("ItemClass");
|
||||
/// int item_type = table.AsInt("ItemType");
|
||||
/// }
|
||||
/// }
|
||||
/// </pre>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class cCsvTable
|
||||
{
|
||||
public :
|
||||
cCsvFile m_File; ///< CSV 파일 객체
|
||||
private:
|
||||
cCsvAlias m_Alias; ///< 문자열을 셀 인덱스로 변환하기 위한 객체
|
||||
int m_CurRow; ///< 현재 횡단 중인 행 번호
|
||||
|
||||
|
||||
public:
|
||||
/// \brief 생성자
|
||||
cCsvTable();
|
||||
|
||||
/// \brief 소멸자
|
||||
virtual ~cCsvTable();
|
||||
|
||||
|
||||
public:
|
||||
/// \brief 지정된 이름의 CSV 파일을 로드한다.
|
||||
bool Load(const char* fileName, const char seperator=',', const char quote='"');
|
||||
|
||||
/// \brief 셀을 액세스할 때, 숫자 대신 사용할 이름을 등록한다.
|
||||
void AddAlias(const char* name, size_t index) { m_Alias.AddAlias(name, index); }
|
||||
|
||||
/// \brief 다음 행으로 넘어간다.
|
||||
bool Next();
|
||||
|
||||
/// \brief 현재 행의 셀 숫자를 반환한다.
|
||||
size_t ColCount() const;
|
||||
|
||||
/// \brief 인덱스를 이용해 int 형으로 셀값을 반환한다.
|
||||
int AsInt(size_t index) const;
|
||||
|
||||
/// \brief 인덱스를 이용해 double 형으로 셀값을 반환한다.
|
||||
double AsDouble(size_t index) const;
|
||||
|
||||
/// \brief 인덱스를 이용해 std::string 형으로 셀값을 반환한다.
|
||||
const char* AsStringByIndex(size_t index) const;
|
||||
|
||||
/// \brief 셀 이름을 이용해 int 형으로 셀값을 반환한다.
|
||||
int AsInt(const char* name) const { return AsInt(m_Alias[name]); }
|
||||
|
||||
/// \brief 셀 이름을 이용해 double 형으로 셀값을 반환한다.
|
||||
double AsDouble(const char* name) const { return AsDouble(m_Alias[name]); }
|
||||
|
||||
/// \brief 셀 이름을 이용해 std::string 형으로 셀값을 반환한다.
|
||||
const char* AsString(const char* name) const { return AsStringByIndex(m_Alias[name]); }
|
||||
|
||||
/// \brief alias를 포함해 모든 데이터를 삭제한다.
|
||||
void Destroy();
|
||||
|
||||
|
||||
private:
|
||||
/// \brief 현재 행을 반환한다.
|
||||
const cCsvRow* const CurRow() const;
|
||||
|
||||
/// \brief 복사 생성자 금지
|
||||
cCsvTable(const cCsvTable&) {}
|
||||
|
||||
/// \brief 대입 연산자 금지
|
||||
const cCsvTable& operator = (const cCsvTable&) { return *this; }
|
||||
};
|
||||
|
||||
#endif //__CSVFILE_H__
|
||||
566
src/DumpProto/ItemCSVReader.cpp
Normal file
566
src/DumpProto/ItemCSVReader.cpp
Normal file
@@ -0,0 +1,566 @@
|
||||
#include <math.h>
|
||||
#include "ItemCSVReader.h"
|
||||
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
inline string trim_left(const string& str)
|
||||
{
|
||||
string::size_type n = str.find_first_not_of(" \t\v\n\r");
|
||||
return n == string::npos ? str : str.substr(n, str.length());
|
||||
}
|
||||
|
||||
inline string trim_right(const string& str)
|
||||
{
|
||||
string::size_type n = str.find_last_not_of(" \t\v\n\r");
|
||||
return n == string::npos ? str : str.substr(0, n + 1);
|
||||
}
|
||||
|
||||
string trim(const string& str){return trim_left(trim_right(str));}
|
||||
|
||||
static string* StringSplit(string strOrigin, string strTok)
|
||||
{
|
||||
int cutAt; //자르는위치
|
||||
int index = 0; //문자열인덱스
|
||||
string* strResult = new string[30]; //결과return 할변수
|
||||
|
||||
//strTok을찾을때까지반복
|
||||
while ((cutAt = strOrigin.find_first_of(strTok)) != strOrigin.npos)
|
||||
{
|
||||
if (cutAt > 0) //자르는위치가0보다크면(성공시)
|
||||
{
|
||||
strResult[index++] = strOrigin.substr(0, cutAt); //결과배열에추가
|
||||
}
|
||||
strOrigin = strOrigin.substr(cutAt+1); //원본은자른부분제외한나머지
|
||||
}
|
||||
|
||||
if(strOrigin.length() > 0) //원본이아직남았으면
|
||||
{
|
||||
strResult[index++] = strOrigin.substr(0, cutAt); //나머지를결과배열에추가
|
||||
}
|
||||
|
||||
for( int i=0;i<index;i++)
|
||||
{
|
||||
strResult[i] = trim(strResult[i]);
|
||||
}
|
||||
|
||||
return strResult; //결과return
|
||||
}
|
||||
|
||||
|
||||
|
||||
int get_Item_Type_Value(string inputString)
|
||||
{
|
||||
string arType[] = {"ITEM_NONE", "ITEM_WEAPON",
|
||||
"ITEM_ARMOR", "ITEM_USE",
|
||||
"ITEM_AUTOUSE", "ITEM_MATERIAL",
|
||||
"ITEM_SPECIAL", "ITEM_TOOL",
|
||||
"ITEM_LOTTERY", "ITEM_ELK", //10개
|
||||
|
||||
"ITEM_METIN", "ITEM_CONTAINER",
|
||||
"ITEM_FISH", "ITEM_ROD",
|
||||
"ITEM_RESOURCE", "ITEM_CAMPFIRE",
|
||||
"ITEM_UNIQUE", "ITEM_SKILLBOOK",
|
||||
"ITEM_QUEST", "ITEM_POLYMORPH", //20개
|
||||
|
||||
"ITEM_TREASURE_BOX", "ITEM_TREASURE_KEY",
|
||||
"ITEM_SKILLFORGET", "ITEM_GIFTBOX",
|
||||
"ITEM_PICK", "ITEM_HAIR",
|
||||
"ITEM_TOTEM", "ITEM_BLEND",
|
||||
"ITEM_COSTUME", "ITEM_DS", //30개
|
||||
|
||||
"ITEM_SPECIAL_DS", "ITEM_EXTRACT", //32개
|
||||
|
||||
"ITEM_SECONDARY_COIN", //33개
|
||||
|
||||
"ITEM_RING", "ITEM_BELT" //35개 (EItemTypes 값으로 치면 34)
|
||||
};
|
||||
|
||||
|
||||
int retInt = -1;
|
||||
//cout << "Type : " << typeStr << " -> ";
|
||||
for (int j=0;j<sizeof(arType)/sizeof(arType[0]);j++) {
|
||||
string tempString = arType[j];
|
||||
if (inputString.find(tempString)!=string::npos && tempString.find(inputString)!=string::npos) {
|
||||
//cout << j << " ";
|
||||
retInt = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//cout << endl;
|
||||
|
||||
return retInt;
|
||||
|
||||
}
|
||||
|
||||
int get_Item_SubType_Value(int type_value, string inputString)
|
||||
{
|
||||
string arSub1[] = { "WEAPON_SWORD", "WEAPON_DAGGER", "WEAPON_BOW", "WEAPON_TWO_HANDED",
|
||||
"WEAPON_BELL", "WEAPON_FAN", "WEAPON_ARROW", "WEAPON_MOUNT_SPEAR"};
|
||||
string arSub2[] = { "ARMOR_BODY", "ARMOR_HEAD", "ARMOR_SHIELD", "ARMOR_WRIST", "ARMOR_FOOTS",
|
||||
"ARMOR_NECK", "ARMOR_EAR", "ARMOR_NUM_TYPES"};
|
||||
string arSub3[] = { "USE_POTION", "USE_TALISMAN", "USE_TUNING", "USE_MOVE", "USE_TREASURE_BOX", "USE_MONEYBAG", "USE_BAIT",
|
||||
"USE_ABILITY_UP", "USE_AFFECT", "USE_CREATE_STONE", "USE_SPECIAL", "USE_POTION_NODELAY", "USE_CLEAR",
|
||||
"USE_INVISIBILITY", "USE_DETACHMENT", "USE_BUCKET", "USE_POTION_CONTINUE", "USE_CLEAN_SOCKET",
|
||||
"USE_CHANGE_ATTRIBUTE", "USE_ADD_ATTRIBUTE", "USE_ADD_ACCESSORY_SOCKET", "USE_PUT_INTO_ACCESSORY_SOCKET",
|
||||
"USE_ADD_ATTRIBUTE2", "USE_RECIPE", "USE_CHANGE_ATTRIBUTE2", "USE_BIND", "USE_UNBIND", "USE_TIME_CHARGE_PER", "USE_TIME_CHARGE_FIX", "USE_PUT_INTO_BELT_SOCKET", "USE_PUT_INTO_RING_SOCKET"};
|
||||
string arSub4[] = { "AUTOUSE_POTION", "AUTOUSE_ABILITY_UP", "AUTOUSE_BOMB", "AUTOUSE_GOLD", "AUTOUSE_MONEYBAG", "AUTOUSE_TREASURE_BOX"};
|
||||
string arSub5[] = { "MATERIAL_LEATHER", "MATERIAL_BLOOD", "MATERIAL_ROOT", "MATERIAL_NEEDLE", "MATERIAL_JEWEL",
|
||||
"MATERIAL_DS_REFINE_NORMAL", "MATERIAL_DS_REFINE_BLESSED", "MATERIAL_DS_REFINE_HOLLY"};
|
||||
string arSub6[] = { "SPECIAL_MAP", "SPECIAL_KEY", "SPECIAL_DOC", "SPECIAL_SPIRIT"};
|
||||
string arSub7[] = { "TOOL_FISHING_ROD" };
|
||||
string arSub8[] = { "LOTTERY_TICKET", "LOTTERY_INSTANT" };
|
||||
string arSub10[] = { "METIN_NORMAL", "METIN_GOLD" };
|
||||
string arSub12[] = { "FISH_ALIVE", "FISH_DEAD"};
|
||||
string arSub14[] = { "RESOURCE_FISHBONE", "RESOURCE_WATERSTONEPIECE", "RESOURCE_WATERSTONE", "RESOURCE_BLOOD_PEARL",
|
||||
"RESOURCE_BLUE_PEARL", "RESOURCE_WHITE_PEARL", "RESOURCE_BUCKET", "RESOURCE_CRYSTAL", "RESOURCE_GEM",
|
||||
"RESOURCE_STONE", "RESOURCE_METIN", "RESOURCE_ORE" };
|
||||
string arSub16[] = { "UNIQUE_NONE", "UNIQUE_BOOK", "UNIQUE_SPECIAL_RIDE", "UNIQUE_3", "UNIQUE_4", "UNIQUE_5",
|
||||
"UNIQUE_6", "UNIQUE_7", "UNIQUE_8", "UNIQUE_9", "USE_SPECIAL"};
|
||||
string arSub28[] = { "COSTUME_BODY", "COSTUME_HAIR" };
|
||||
string arSub29[] = { "DS_SLOT1", "DS_SLOT2", "DS_SLOT3", "DS_SLOT4", "DS_SLOT5", "DS_SLOT6" };
|
||||
string arSub31[] = { "EXTRACT_DRAGON_SOUL", "EXTRACT_DRAGON_HEART" };
|
||||
|
||||
|
||||
string* arSubType[] = {0, //0
|
||||
arSub1, //1
|
||||
arSub2, //2
|
||||
arSub3, //3
|
||||
arSub4, //4
|
||||
arSub5, //5
|
||||
arSub6, //6
|
||||
arSub7, //7
|
||||
arSub8, //8
|
||||
0, //9
|
||||
arSub10, //10
|
||||
0, //11
|
||||
arSub12, //12
|
||||
0, //13
|
||||
arSub14, //14
|
||||
0, //15
|
||||
arSub16, //16
|
||||
0, //17
|
||||
0, //18
|
||||
0, //19
|
||||
0, //20
|
||||
0, //21
|
||||
0, //22
|
||||
0, //23
|
||||
0, //24
|
||||
0, //25
|
||||
0, //26
|
||||
0, //27
|
||||
arSub28, //28
|
||||
arSub29, //29
|
||||
arSub29, //30
|
||||
arSub31, //31
|
||||
0, //32
|
||||
0, //33
|
||||
0, //34
|
||||
};
|
||||
int arNumberOfSubtype[35];
|
||||
arNumberOfSubtype[0] = 0;
|
||||
arNumberOfSubtype[1] = sizeof(arSub1)/sizeof(arSub1[0]);
|
||||
arNumberOfSubtype[2] = sizeof(arSub2)/sizeof(arSub2[0]);
|
||||
arNumberOfSubtype[3] = sizeof(arSub3)/sizeof(arSub3[0]);
|
||||
arNumberOfSubtype[4] = sizeof(arSub4)/sizeof(arSub4[0]);
|
||||
arNumberOfSubtype[5] = sizeof(arSub5)/sizeof(arSub5[0]);
|
||||
arNumberOfSubtype[6] = sizeof(arSub6)/sizeof(arSub6[0]);
|
||||
arNumberOfSubtype[7] = sizeof(arSub7)/sizeof(arSub7[0]);
|
||||
arNumberOfSubtype[8] = sizeof(arSub8)/sizeof(arSub8[0]);
|
||||
arNumberOfSubtype[9] = 0;
|
||||
arNumberOfSubtype[10] = sizeof(arSub10)/sizeof(arSub10[0]);
|
||||
arNumberOfSubtype[11] = 0;
|
||||
arNumberOfSubtype[12] = sizeof(arSub12)/sizeof(arSub12[0]);
|
||||
arNumberOfSubtype[13] = 0;
|
||||
arNumberOfSubtype[14] = sizeof(arSub14)/sizeof(arSub14[0]);
|
||||
arNumberOfSubtype[15] = 0;
|
||||
arNumberOfSubtype[16] = sizeof(arSub16)/sizeof(arSub16[0]);
|
||||
arNumberOfSubtype[17] = 0;
|
||||
arNumberOfSubtype[18] = 0;
|
||||
arNumberOfSubtype[19] = 0;
|
||||
arNumberOfSubtype[20] = 0;
|
||||
arNumberOfSubtype[21] = 0;
|
||||
arNumberOfSubtype[22] = 0;
|
||||
arNumberOfSubtype[23] = 0;
|
||||
arNumberOfSubtype[24] = 0;
|
||||
arNumberOfSubtype[25] = 0;
|
||||
arNumberOfSubtype[26] = 0;
|
||||
arNumberOfSubtype[27] = 0;
|
||||
arNumberOfSubtype[28] = sizeof(arSub28)/sizeof(arSub28[0]);
|
||||
arNumberOfSubtype[29] = sizeof(arSub29)/sizeof(arSub29[0]);
|
||||
arNumberOfSubtype[30] = sizeof(arSub29)/sizeof(arSub29[0]);
|
||||
arNumberOfSubtype[31] = sizeof(arSub31)/sizeof(arSub31[0]);
|
||||
arNumberOfSubtype[32] = 0;
|
||||
arNumberOfSubtype[33] = 0;
|
||||
arNumberOfSubtype[34] = 0;
|
||||
|
||||
|
||||
//아이템 타입의 서브타입 어레이가 존재하는지 알아보고, 없으면 0 리턴
|
||||
if (arSubType[type_value]==0) {
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
|
||||
int retInt = -1;
|
||||
//cout << "SubType : " << subTypeStr << " -> ";
|
||||
for (int j=0;j<arNumberOfSubtype[type_value];j++) {
|
||||
string tempString = arSubType[type_value][j];
|
||||
string tempInputString = trim(inputString);
|
||||
if (tempInputString.compare(tempString)==0)
|
||||
{
|
||||
//cout << j << " ";
|
||||
retInt = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//cout << endl;
|
||||
|
||||
return retInt;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int get_Item_AntiFlag_Value(string inputString)
|
||||
{
|
||||
|
||||
string arAntiFlag[] = {"ANTI_FEMALE", "ANTI_MALE", "ANTI_MUSA", "ANTI_ASSASSIN", "ANTI_SURA", "ANTI_MUDANG",
|
||||
"ANTI_GET", "ANTI_DROP", "ANTI_SELL", "ANTI_EMPIRE_A", "ANTI_EMPIRE_B", "ANTI_EMPIRE_C",
|
||||
"ANTI_SAVE", "ANTI_GIVE", "ANTI_PKDROP", "ANTI_STACK", "ANTI_MYSHOP", "ANTI_SAFEBOX"};
|
||||
|
||||
|
||||
int retValue = 0;
|
||||
string* arInputString = StringSplit(inputString, "|"); //프로토 정보 내용을 단어별로 쪼갠 배열.
|
||||
for(int i =0;i<sizeof(arAntiFlag)/sizeof(arAntiFlag[0]);i++) {
|
||||
string tempString = arAntiFlag[i];
|
||||
for (int j=0; j<30 ; j++) //최대 30개 단어까지. (하드코딩)
|
||||
{
|
||||
string tempString2 = arInputString[j];
|
||||
if (tempString2.compare(tempString)==0) { //일치하는지 확인.
|
||||
retValue = retValue + pow((float)2,(float)i);
|
||||
}
|
||||
|
||||
if(tempString2.compare("") == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete []arInputString;
|
||||
//cout << "AntiFlag : " << antiFlagStr << " -> " << retValue << endl;
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
int get_Item_Flag_Value(string inputString)
|
||||
{
|
||||
|
||||
string arFlag[] = {"ITEM_TUNABLE", "ITEM_SAVE", "ITEM_STACKABLE", "COUNT_PER_1GOLD", "ITEM_SLOW_QUERY", "ITEM_UNIQUE",
|
||||
"ITEM_MAKECOUNT", "ITEM_IRREMOVABLE", "CONFIRM_WHEN_USE", "QUEST_USE", "QUEST_USE_MULTIPLE",
|
||||
"QUEST_GIVE", "ITEM_QUEST", "LOG", "STACKABLE", "SLOW_QUERY", "REFINEABLE", "IRREMOVABLE", "ITEM_APPLICABLE"};
|
||||
|
||||
|
||||
int retValue = 0;
|
||||
string* arInputString = StringSplit(inputString, "|"); //프로토 정보 내용을 단어별로 쪼갠 배열.
|
||||
for(int i =0;i<sizeof(arFlag)/sizeof(arFlag[0]);i++) {
|
||||
string tempString = arFlag[i];
|
||||
for (int j=0; j<30 ; j++) //최대 30개 단어까지. (하드코딩)
|
||||
{
|
||||
string tempString2 = arInputString[j];
|
||||
if (tempString2.compare(tempString)==0) { //일치하는지 확인.
|
||||
retValue = retValue + pow((float)2,(float)i);
|
||||
}
|
||||
|
||||
if(tempString2.compare("") == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete []arInputString;
|
||||
//cout << "Flag : " << flagStr << " -> " << retValue << endl;
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
int get_Item_WearFlag_Value(string inputString)
|
||||
{
|
||||
|
||||
string arWearrFlag[] = {"WEAR_BODY", "WEAR_HEAD", "WEAR_FOOTS", "WEAR_WRIST", "WEAR_WEAPON", "WEAR_NECK", "WEAR_EAR", "WEAR_SHIELD", "WEAR_UNIQUE",
|
||||
"WEAR_ARROW", "WEAR_HAIR", "WEAR_ABILITY"};
|
||||
|
||||
|
||||
int retValue = 0;
|
||||
string* arInputString = StringSplit(inputString, "|"); //프로토 정보 내용을 단어별로 쪼갠 배열.
|
||||
for(int i =0;i<sizeof(arWearrFlag)/sizeof(arWearrFlag[0]);i++) {
|
||||
string tempString = arWearrFlag[i];
|
||||
for (int j=0; j<30 ; j++) //최대 30개 단어까지. (하드코딩)
|
||||
{
|
||||
string tempString2 = arInputString[j];
|
||||
if (tempString2.compare(tempString)==0) { //일치하는지 확인.
|
||||
retValue = retValue + pow((float)2,(float)i);
|
||||
}
|
||||
|
||||
if(tempString2.compare("") == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete []arInputString;
|
||||
//cout << "WearFlag : " << wearFlagStr << " -> " << retValue << endl;
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
int get_Item_Immune_Value(string inputString)
|
||||
{
|
||||
|
||||
string arImmune[] = {"PARA","CURSE","STUN","SLEEP","SLOW","POISON","TERROR"};
|
||||
|
||||
int retValue = 0;
|
||||
string* arInputString = StringSplit(inputString, "|"); //프로토 정보 내용을 단어별로 쪼갠 배열.
|
||||
for(int i =0;i<sizeof(arImmune)/sizeof(arImmune[0]);i++) {
|
||||
string tempString = arImmune[i];
|
||||
for (int j=0; j<30 ; j++) //최대 30개 단어까지. (하드코딩)
|
||||
{
|
||||
string tempString2 = arInputString[j];
|
||||
if (tempString2.compare(tempString)==0) { //일치하는지 확인.
|
||||
retValue = retValue + pow((float)2,(float)i);
|
||||
}
|
||||
|
||||
if(tempString2.compare("") == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete []arInputString;
|
||||
//cout << "Immune : " << immuneStr << " -> " << retValue << endl;
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int get_Item_LimitType_Value(string inputString)
|
||||
{
|
||||
string arLimitType[] = {"LIMIT_NONE", "LEVEL", "STR", "DEX", "INT", "CON", "PC_BANG", "REAL_TIME", "REAL_TIME_FIRST_USE", "TIMER_BASED_ON_WEAR"};
|
||||
|
||||
int retInt = -1;
|
||||
//cout << "LimitType : " << limitTypeStr << " -> ";
|
||||
for (int j=0;j<sizeof(arLimitType)/sizeof(arLimitType[0]);j++) {
|
||||
string tempString = arLimitType[j];
|
||||
string tempInputString = trim(inputString);
|
||||
if (tempInputString.compare(tempString)==0)
|
||||
{
|
||||
//cout << j << " ";
|
||||
retInt = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//cout << endl;
|
||||
|
||||
return retInt;
|
||||
}
|
||||
|
||||
|
||||
int get_Item_ApplyType_Value(string inputString)
|
||||
{
|
||||
string arApplyType[] = {"APPLY_NONE", "APPLY_MAX_HP", "APPLY_MAX_SP", "APPLY_CON", "APPLY_INT", "APPLY_STR", "APPLY_DEX", "APPLY_ATT_SPEED",
|
||||
"APPLY_MOV_SPEED", "APPLY_CAST_SPEED", "APPLY_HP_REGEN", "APPLY_SP_REGEN", "APPLY_POISON_PCT", "APPLY_STUN_PCT",
|
||||
"APPLY_SLOW_PCT", "APPLY_CRITICAL_PCT", "APPLY_PENETRATE_PCT", "APPLY_ATTBONUS_HUMAN", "APPLY_ATTBONUS_ANIMAL",
|
||||
"APPLY_ATTBONUS_ORC", "APPLY_ATTBONUS_MILGYO", "APPLY_ATTBONUS_UNDEAD", "APPLY_ATTBONUS_DEVIL", "APPLY_STEAL_HP",
|
||||
"APPLY_STEAL_SP", "APPLY_MANA_BURN_PCT", "APPLY_DAMAGE_SP_RECOVER", "APPLY_BLOCK", "APPLY_DODGE", "APPLY_RESIST_SWORD",
|
||||
"APPLY_RESIST_TWOHAND", "APPLY_RESIST_DAGGER", "APPLY_RESIST_BELL", "APPLY_RESIST_FAN", "APPLY_RESIST_BOW", "APPLY_RESIST_FIRE",
|
||||
"APPLY_RESIST_ELEC", "APPLY_RESIST_MAGIC", "APPLY_RESIST_WIND", "APPLY_REFLECT_MELEE", "APPLY_REFLECT_CURSE", "APPLY_POISON_REDUCE",
|
||||
"APPLY_KILL_SP_RECOVER", "APPLY_EXP_DOUBLE_BONUS", "APPLY_GOLD_DOUBLE_BONUS", "APPLY_ITEM_DROP_BONUS", "APPLY_POTION_BONUS",
|
||||
"APPLY_KILL_HP_RECOVER", "APPLY_IMMUNE_STUN", "APPLY_IMMUNE_SLOW", "APPLY_IMMUNE_FALL", "APPLY_SKILL", "APPLY_BOW_DISTANCE",
|
||||
"APPLY_ATT_GRADE_BONUS", "APPLY_DEF_GRADE_BONUS", "APPLY_MAGIC_ATT_GRADE", "APPLY_MAGIC_DEF_GRADE", "APPLY_CURSE_PCT",
|
||||
"APPLY_MAX_STAMINA", "APPLY_ATTBONUS_WARRIOR", "APPLY_ATTBONUS_ASSASSIN", "APPLY_ATTBONUS_SURA", "APPLY_ATTBONUS_SHAMAN",
|
||||
"APPLY_ATTBONUS_MONSTER", "APPLY_MALL_ATTBONUS", "APPLY_MALL_DEFBONUS", "APPLY_MALL_EXPBONUS", "APPLY_MALL_ITEMBONUS",
|
||||
"APPLY_MALL_GOLDBONUS", "APPLY_MAX_HP_PCT", "APPLY_MAX_SP_PCT", "APPLY_SKILL_DAMAGE_BONUS", "APPLY_NORMAL_HIT_DAMAGE_BONUS",
|
||||
"APPLY_SKILL_DEFEND_BONUS", "APPLY_NORMAL_HIT_DEFEND_BONUS", "APPLY_PC_BANG_EXP_BONUS", "APPLY_PC_BANG_DROP_BONUS",
|
||||
"APPLY_EXTRACT_HP_PCT", "APPLY_RESIST_WARRIOR", "APPLY_RESIST_ASSASSIN", "APPLY_RESIST_SURA", "APPLY_RESIST_SHAMAN",
|
||||
"APPLY_ENERGY", "APPLY_DEF_GRADE", "APPLY_COSTUME_ATTR_BONUS", "APPLY_MAGIC_ATTBONUS_PER", "APPLY_MELEE_MAGIC_ATTBONUS_PER",
|
||||
"APPLY_RESIST_ICE", "APPLY_RESIST_EARTH", "APPLY_RESIST_DARK", "APPLY_ANTI_CRITICAL_PCT", "APPLY_ANTI_PENETRATE_PCT",
|
||||
};
|
||||
|
||||
int retInt = -1;
|
||||
//cout << "ApplyType : " << applyTypeStr << " -> ";
|
||||
for (int j=0;j<sizeof(arApplyType)/sizeof(arApplyType[0]);j++) {
|
||||
string tempString = arApplyType[j];
|
||||
string tempInputString = trim(inputString);
|
||||
if (tempInputString.compare(tempString)==0)
|
||||
{
|
||||
//cout << j << " ";
|
||||
retInt = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//cout << endl;
|
||||
|
||||
return retInt;
|
||||
|
||||
}
|
||||
|
||||
|
||||
//몬스터 프로토도 읽는다.
|
||||
|
||||
|
||||
int get_Mob_Rank_Value(string inputString)
|
||||
{
|
||||
string arRank[] = {"PAWN", "S_PAWN", "KNIGHT", "S_KNIGHT", "BOSS", "KING"};
|
||||
|
||||
int retInt = -1;
|
||||
//cout << "Rank : " << rankStr << " -> ";
|
||||
for (int j=0;j<sizeof(arRank)/sizeof(arRank[0]);j++) {
|
||||
string tempString = arRank[j];
|
||||
string tempInputString = trim(inputString);
|
||||
if (tempInputString.compare(tempString)==0)
|
||||
{
|
||||
//cout << j << " ";
|
||||
retInt = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//cout << endl;
|
||||
|
||||
return retInt;
|
||||
}
|
||||
|
||||
|
||||
int get_Mob_Type_Value(string inputString)
|
||||
{
|
||||
string arType[] = { "MONSTER", "NPC", "STONE", "WARP", "DOOR", "BUILDING", "PC", "POLYMORPH_PC", "HORSE", "GOTO"};
|
||||
|
||||
int retInt = -1;
|
||||
//cout << "Type : " << typeStr << " -> ";
|
||||
for (int j=0;j<sizeof(arType)/sizeof(arType[0]);j++) {
|
||||
string tempString = arType[j];
|
||||
string tempInputString = trim(inputString);
|
||||
if (tempInputString.compare(tempString)==0)
|
||||
{
|
||||
//cout << j << " ";
|
||||
retInt = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//cout << endl;
|
||||
|
||||
return retInt;
|
||||
}
|
||||
|
||||
int get_Mob_BattleType_Value(string inputString)
|
||||
{
|
||||
string arBattleType[] = { "MELEE", "RANGE", "MAGIC", "SPECIAL", "POWER", "TANKER", "SUPER_POWER", "SUPER_TANKER"};
|
||||
|
||||
int retInt = -1;
|
||||
//cout << "Battle Type : " << battleTypeStr << " -> ";
|
||||
for (int j=0;j<sizeof(arBattleType)/sizeof(arBattleType[0]);j++) {
|
||||
string tempString = arBattleType[j];
|
||||
string tempInputString = trim(inputString);
|
||||
if (tempInputString.compare(tempString)==0)
|
||||
{
|
||||
//cout << j << " ";
|
||||
retInt = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//cout << endl;
|
||||
|
||||
return retInt;
|
||||
}
|
||||
|
||||
int get_Mob_Size_Value(string inputString)
|
||||
{
|
||||
string arSize[] = { "SMALL", "MEDIUM", "BIG"};
|
||||
|
||||
int retInt = 0;
|
||||
//cout << "Size : " << sizeStr << " -> ";
|
||||
for (int j=0;j<sizeof(arSize)/sizeof(arSize[0]);j++) {
|
||||
string tempString = arSize[j];
|
||||
string tempInputString = trim(inputString);
|
||||
if (tempInputString.compare(tempString)==0)
|
||||
{
|
||||
//cout << j << " ";
|
||||
retInt = j + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//cout << endl;
|
||||
|
||||
return retInt;
|
||||
}
|
||||
|
||||
int get_Mob_AIFlag_Value(string inputString)
|
||||
{
|
||||
string arAIFlag[] = {"AGGR","NOMOVE","COWARD","NOATTSHINSU","NOATTCHUNJO","NOATTJINNO","ATTMOB","BERSERK","STONESKIN","GODSPEED","DEATHBLOW","REVIVE"};
|
||||
|
||||
|
||||
int retValue = 0;
|
||||
string* arInputString = StringSplit(inputString, ","); //프로토 정보 내용을 단어별로 쪼갠 배열.
|
||||
for(int i =0;i<sizeof(arAIFlag)/sizeof(arAIFlag[0]);i++) {
|
||||
string tempString = arAIFlag[i];
|
||||
for (int j=0; j<30 ; j++) //최대 30개 단어까지. (하드코딩)
|
||||
{
|
||||
string tempString2 = arInputString[j];
|
||||
if (tempString2.compare(tempString)==0) { //일치하는지 확인.
|
||||
retValue = retValue + pow((float)2,(float)i);
|
||||
}
|
||||
|
||||
if(tempString2.compare("") == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete []arInputString;
|
||||
//cout << "AIFlag : " << aiFlagStr << " -> " << retValue << endl;
|
||||
|
||||
return retValue;
|
||||
}
|
||||
int get_Mob_RaceFlag_Value(string inputString)
|
||||
{
|
||||
string arRaceFlag[] = {"ANIMAL","UNDEAD","DEVIL","HUMAN","ORC","MILGYO","INSECT","FIRE","ICE","DESERT","TREE",
|
||||
"ATT_ELEC","ATT_FIRE","ATT_ICE","ATT_WIND","ATT_EARTH","ATT_DARK"};
|
||||
|
||||
int retValue = 0;
|
||||
string* arInputString = StringSplit(inputString, "|"); //프로토 정보 내용을 단어별로 쪼갠 배열.
|
||||
for(int i =0;i<sizeof(arRaceFlag)/sizeof(arRaceFlag[0]);i++) {
|
||||
string tempString = arRaceFlag[i];
|
||||
for (int j=0; j<30 ; j++) //최대 30개 단어까지. (하드코딩)
|
||||
{
|
||||
string tempString2 = arInputString[j];
|
||||
if (tempString2.compare(tempString)==0) { //일치하는지 확인.
|
||||
retValue = retValue + pow((float)2,(float)i);
|
||||
}
|
||||
|
||||
if(tempString2.compare("") == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete []arInputString;
|
||||
//cout << "Race Flag : " << raceFlagStr << " -> " << retValue << endl;
|
||||
|
||||
return retValue;
|
||||
}
|
||||
int get_Mob_ImmuneFlag_Value(string inputString)
|
||||
{
|
||||
string arImmuneFlag[] = {"STUN","SLOW","FALL","CURSE","POISON","TERROR"};
|
||||
|
||||
int retValue = 0;
|
||||
string* arInputString = StringSplit(inputString, ","); //프로토 정보 내용을 단어별로 쪼갠 배열.
|
||||
for(int i =0;i<sizeof(arImmuneFlag)/sizeof(arImmuneFlag[0]);i++) {
|
||||
string tempString = arImmuneFlag[i];
|
||||
for (int j=0; j<30 ; j++) //최대 30개 단어까지. (하드코딩)
|
||||
{
|
||||
string tempString2 = arInputString[j];
|
||||
if (tempString2.compare(tempString)==0) { //일치하는지 확인.
|
||||
retValue = retValue + pow((float)2,(float)i);
|
||||
}
|
||||
|
||||
if(tempString2.compare("") == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete []arInputString;
|
||||
//cout << "Immune Flag : " << immuneFlagStr << " -> " << retValue << endl;
|
||||
|
||||
|
||||
return retValue;
|
||||
}
|
||||
29
src/DumpProto/ItemCSVReader.h
Normal file
29
src/DumpProto/ItemCSVReader.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef __Item_CSV_READER_H__
|
||||
#define __Item_CSV_READER_H__
|
||||
|
||||
#include <iostream>
|
||||
|
||||
//csv 파일을 읽어와서 아이템 테이블에 넣어준다.
|
||||
void putItemIntoTable(); //(테이블, 테스트여부)
|
||||
|
||||
int get_Item_Type_Value(std::string inputString);
|
||||
int get_Item_SubType_Value(int type_value, std::string inputString);
|
||||
int get_Item_AntiFlag_Value(std::string inputString);
|
||||
int get_Item_Flag_Value(std::string inputString);
|
||||
int get_Item_WearFlag_Value(std::string inputString);
|
||||
int get_Item_Immune_Value(std::string inputString);
|
||||
int get_Item_LimitType_Value(std::string inputString);
|
||||
int get_Item_ApplyType_Value(std::string inputString);
|
||||
|
||||
|
||||
//몬스터 프로토도 읽을 수 있다.
|
||||
int get_Mob_Rank_Value(std::string inputString);
|
||||
int get_Mob_Type_Value(std::string inputString);
|
||||
int get_Mob_BattleType_Value(std::string inputString);
|
||||
|
||||
int get_Mob_Size_Value(std::string inputString);
|
||||
int get_Mob_AIFlag_Value(std::string inputString);
|
||||
int get_Mob_RaceFlag_Value(std::string inputString);
|
||||
int get_Mob_ImmuneFlag_Value(std::string inputString);
|
||||
|
||||
#endif
|
||||
112
src/DumpProto/Singleton.h
Normal file
112
src/DumpProto/Singleton.h
Normal file
@@ -0,0 +1,112 @@
|
||||
#ifndef __INC_ETERLIB_SINGLETON_H__
|
||||
#define __INC_ETERLIB_SINGLETON_H__
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
template <typename T> class CSingleton
|
||||
{
|
||||
static T * ms_singleton;
|
||||
|
||||
public:
|
||||
CSingleton()
|
||||
{
|
||||
assert(!ms_singleton);
|
||||
ms_singleton = (T*) this;
|
||||
}
|
||||
|
||||
virtual ~CSingleton()
|
||||
{
|
||||
assert(ms_singleton);
|
||||
ms_singleton = 0;
|
||||
}
|
||||
|
||||
__forceinline static T & Instance()
|
||||
{
|
||||
assert(ms_singleton);
|
||||
return (*ms_singleton);
|
||||
}
|
||||
|
||||
__forceinline static T * InstancePtr()
|
||||
{
|
||||
return (ms_singleton);
|
||||
}
|
||||
|
||||
__forceinline static T & instance()
|
||||
{
|
||||
assert(ms_singleton);
|
||||
return (*ms_singleton);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> T * CSingleton <T>::ms_singleton = 0;
|
||||
|
||||
//
|
||||
// singleton for non-hungarian
|
||||
//
|
||||
template <typename T> class singleton
|
||||
{
|
||||
static T * ms_singleton;
|
||||
|
||||
public:
|
||||
singleton()
|
||||
{
|
||||
assert(!ms_singleton);
|
||||
ms_singleton = (T*) this;
|
||||
}
|
||||
|
||||
virtual ~singleton()
|
||||
{
|
||||
assert(ms_singleton);
|
||||
ms_singleton = 0;
|
||||
}
|
||||
|
||||
__forceinline static T & Instance()
|
||||
{
|
||||
assert(ms_singleton);
|
||||
return (*ms_singleton);
|
||||
}
|
||||
|
||||
__forceinline static T * InstancePtr()
|
||||
{
|
||||
return (ms_singleton);
|
||||
}
|
||||
|
||||
__forceinline static T & instance()
|
||||
{
|
||||
assert(ms_singleton);
|
||||
return (*ms_singleton);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> T * singleton <T>::ms_singleton = 0;
|
||||
|
||||
/*
|
||||
template<typename T>
|
||||
class CSingleton : public T
|
||||
{
|
||||
public:
|
||||
static T & Instance()
|
||||
{
|
||||
assert(ms_pInstance != NULL);
|
||||
return *ms_pInstance;
|
||||
}
|
||||
|
||||
CSingleton()
|
||||
{
|
||||
assert(ms_pInstance == NULL);
|
||||
ms_pInstance = this;
|
||||
}
|
||||
|
||||
virtual ~CSingleton()
|
||||
{
|
||||
assert(ms_pInstance);
|
||||
ms_pInstance = 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
static T * ms_pInstance;
|
||||
};
|
||||
|
||||
template<typename T> T * CSingleton<T>::ms_pInstance = NULL;
|
||||
*/
|
||||
#endif
|
||||
1186
src/DumpProto/dump_proto.cpp
Normal file
1186
src/DumpProto/dump_proto.cpp
Normal file
File diff suppressed because it is too large
Load Diff
261
src/DumpProto/lzo.cpp
Normal file
261
src/DumpProto/lzo.cpp
Normal file
@@ -0,0 +1,261 @@
|
||||
//#include "common.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "lzo.h"
|
||||
#include "tea.h"
|
||||
|
||||
// Global instance of CLZO singleton to initialize the singleton pattern
|
||||
static CLZO g_lzoInstance;
|
||||
|
||||
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
|
||||
((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
|
||||
((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ))
|
||||
|
||||
DWORD CLZObject::ms_dwFourCC = MAKEFOURCC('M', 'C', 'O', 'Z');
|
||||
|
||||
CLZObject::CLZObject()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void CLZObject::Initialize()
|
||||
{
|
||||
m_pHeader = NULL;
|
||||
m_pbBuffer = NULL;
|
||||
m_dwBufferSize = 0;
|
||||
m_pbIn = NULL;
|
||||
m_bCompressed = false;
|
||||
}
|
||||
|
||||
void CLZObject::Clear()
|
||||
{
|
||||
if (m_pbBuffer)
|
||||
delete [] m_pbBuffer;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CLZObject::~CLZObject()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
DWORD CLZObject::GetSize()
|
||||
{
|
||||
assert(m_pHeader);
|
||||
|
||||
if (m_bCompressed)
|
||||
{
|
||||
if (m_pHeader->dwEncryptSize)
|
||||
return sizeof(THeader) + sizeof(DWORD) + m_pHeader->dwEncryptSize;
|
||||
else
|
||||
return sizeof(THeader) + sizeof(DWORD) + m_pHeader->dwCompressedSize;
|
||||
}
|
||||
else
|
||||
return m_pHeader->dwRealSize;
|
||||
}
|
||||
|
||||
void CLZObject::BeginCompress(const void * pvIn, UINT uiInLen)
|
||||
{
|
||||
m_pbIn = (const BYTE *) pvIn;
|
||||
|
||||
// sizeof(SHeader) +
|
||||
// <20><>ȣȭ<C8A3><C8AD> <20><><EFBFBD><EFBFBD> fourCC 4<><34><EFBFBD><EFBFBD>Ʈ
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20>ִ<EFBFBD> <20>ִ<EFBFBD> <20>뷮 +
|
||||
// <20><>ȣȭ<C8A3><C8AD> <20><><EFBFBD><EFBFBD> 8 <20><><EFBFBD><EFBFBD>Ʈ
|
||||
m_dwBufferSize = sizeof(THeader) + sizeof(DWORD) + (uiInLen + uiInLen / 64 + 16 + 3) + 8;
|
||||
|
||||
m_pbBuffer = new BYTE[m_dwBufferSize];
|
||||
memset(m_pbBuffer, 0, m_dwBufferSize);
|
||||
|
||||
m_pHeader = (THeader *) m_pbBuffer;
|
||||
m_pHeader->dwFourCC = ms_dwFourCC;
|
||||
m_pHeader->dwEncryptSize = m_pHeader->dwCompressedSize = m_pHeader->dwRealSize = 0;
|
||||
m_pHeader->dwRealSize = uiInLen;
|
||||
}
|
||||
|
||||
|
||||
bool CLZObject::Compress()
|
||||
{
|
||||
BYTE * pbBuffer = m_pbBuffer + sizeof(THeader);
|
||||
*(DWORD *) pbBuffer = 0x5A4F434D; // 'MOCZ'
|
||||
pbBuffer += sizeof(DWORD);
|
||||
|
||||
// Calculate available space for compressed data
|
||||
lzo_uint available_space = (lzo_uint)(m_dwBufferSize - sizeof(THeader) - sizeof(DWORD));
|
||||
lzo_uint compressed_size = available_space;
|
||||
|
||||
int r = lzo1x_1_compress(
|
||||
(BYTE *) m_pbIn,
|
||||
(lzo_uint)m_pHeader->dwRealSize,
|
||||
pbBuffer,
|
||||
&compressed_size,
|
||||
CLZO::instance().GetWorkMemory()
|
||||
);
|
||||
|
||||
if (LZO_E_OK != r)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pHeader->dwCompressedSize = (DWORD)compressed_size;
|
||||
m_bCompressed = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CLZObject::BeginDecompress(const void * pvIn)
|
||||
{
|
||||
THeader * pHeader = (THeader *) pvIn;
|
||||
|
||||
if (pHeader->dwFourCC != ms_dwFourCC)
|
||||
{
|
||||
fprintf(stderr, "LZObject: not a valid data");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pHeader = pHeader;
|
||||
m_pbIn = (const BYTE *) pvIn + (sizeof(THeader) + sizeof(DWORD));
|
||||
|
||||
m_pbBuffer = new BYTE[pHeader->dwRealSize];
|
||||
memset(m_pbBuffer, 0, pHeader->dwRealSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CLZObject::Decompress(DWORD * pdwKey)
|
||||
{
|
||||
UINT uiSize;
|
||||
int r;
|
||||
|
||||
if (m_pHeader->dwEncryptSize)
|
||||
{
|
||||
BYTE * pbDecryptedBuffer = Decrypt(pdwKey);
|
||||
|
||||
if (*(DWORD *) pbDecryptedBuffer != ms_dwFourCC)
|
||||
{
|
||||
fprintf(stderr, "LZObject: key incorrect");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (LZO_E_OK != (r = lzo1x_decompress(pbDecryptedBuffer + sizeof(DWORD), m_pHeader->dwCompressedSize, m_pbBuffer, (lzo_uint*)&uiSize, NULL)))
|
||||
{
|
||||
fprintf(stderr, "LZObject: Decompress failed(decrypt) ret %d\n", r);
|
||||
return false;
|
||||
}
|
||||
|
||||
delete [] pbDecryptedBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
uiSize = m_pHeader->dwRealSize;
|
||||
|
||||
if (LZO_E_OK != (r = lzo1x_decompress_safe(m_pbIn, m_pHeader->dwCompressedSize, m_pbBuffer, (lzo_uint*)&uiSize, NULL)))
|
||||
{
|
||||
fprintf(stderr, "LZObject: Decompress failed : ret %d, CompressedSize %d\n", r, m_pHeader->dwCompressedSize);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (uiSize != m_pHeader->dwRealSize)
|
||||
{
|
||||
fprintf(stderr, "LZObject: Size differs");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CLZObject::Encrypt(DWORD * pdwKey)
|
||||
{
|
||||
if (!m_bCompressed)
|
||||
{
|
||||
assert(!"not compressed yet");
|
||||
return false;
|
||||
}
|
||||
|
||||
BYTE * pbBuffer = m_pbBuffer + sizeof(THeader);
|
||||
// TEA pads to 8-byte boundaries; encrypt the FourCC + compressed data
|
||||
DWORD dwSizeToEncrypt = sizeof(DWORD) + m_pHeader->dwCompressedSize;
|
||||
fprintf(stderr, "Encrypt: dwCompressedSize=%u, dwSizeToEncrypt=%u\n", m_pHeader->dwCompressedSize, dwSizeToEncrypt);
|
||||
m_pHeader->dwEncryptSize = tea_encrypt((DWORD *) pbBuffer, (const DWORD *) pbBuffer, pdwKey, dwSizeToEncrypt);
|
||||
fprintf(stderr, "Encrypt: dwEncryptSize=%u (return from tea_encrypt)\n", m_pHeader->dwEncryptSize);
|
||||
if (m_pHeader->dwEncryptSize == 0)
|
||||
{
|
||||
fprintf(stderr, "Encrypt: tea_encrypt failed (returned 0)\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BYTE * CLZObject::Decrypt(DWORD * pdwKey)
|
||||
{
|
||||
assert(m_pbBuffer);
|
||||
|
||||
BYTE * pbDecryptBuffer = new BYTE[m_pHeader->dwEncryptSize];
|
||||
|
||||
tea_encrypt((DWORD *) pbDecryptBuffer, (const DWORD *) (m_pbIn - sizeof(DWORD)), pdwKey, m_pHeader->dwEncryptSize);
|
||||
return pbDecryptBuffer;
|
||||
}
|
||||
|
||||
CLZO::CLZO() : m_pWorkMem(NULL)
|
||||
{
|
||||
if (lzo_init() != LZO_E_OK)
|
||||
{
|
||||
fprintf(stderr, "LZO: cannot initialize\n");
|
||||
return;
|
||||
}
|
||||
|
||||
m_pWorkMem = (BYTE *) malloc(LZO1X_MEM_COMPRESS);
|
||||
|
||||
if (NULL == m_pWorkMem)
|
||||
{
|
||||
fprintf(stderr, "LZO: cannot alloc memory\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CLZO::~CLZO()
|
||||
{
|
||||
if (m_pWorkMem)
|
||||
{
|
||||
free(m_pWorkMem);
|
||||
m_pWorkMem = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool CLZO::CompressMemory(CLZObject & rObj, const void * pIn, UINT uiInLen)
|
||||
{
|
||||
rObj.BeginCompress(pIn, uiInLen);
|
||||
return rObj.Compress();
|
||||
}
|
||||
|
||||
bool CLZO::CompressEncryptedMemory(CLZObject & rObj, const void * pIn, UINT uiInLen, DWORD * pdwKey)
|
||||
{
|
||||
rObj.BeginCompress(pIn, uiInLen);
|
||||
|
||||
if (!rObj.Compress())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return rObj.Encrypt(pdwKey);
|
||||
}
|
||||
|
||||
bool CLZO::Decompress(CLZObject & rObj, const BYTE * pbBuf, DWORD * pdwKey)
|
||||
{
|
||||
if (!rObj.BeginDecompress(pbBuf))
|
||||
return false;
|
||||
|
||||
if (!rObj.Decompress(pdwKey))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
BYTE * CLZO::GetWorkMemory()
|
||||
{
|
||||
return m_pWorkMem;
|
||||
}
|
||||
|
||||
70
src/DumpProto/lzo.h
Normal file
70
src/DumpProto/lzo.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#ifndef __INC_METIN_II_371GNFBQOCJ_LZO_H__
|
||||
#define __INC_METIN_II_371GNFBQOCJ_LZO_H__
|
||||
|
||||
#include "Singleton.h"
|
||||
#include <lzo/lzo1x.h>
|
||||
#include <lzo/lzoconf.h>
|
||||
|
||||
typedef unsigned char BYTE;
|
||||
typedef unsigned short WORD;
|
||||
typedef unsigned long DWORD;
|
||||
typedef unsigned int UINT;
|
||||
|
||||
class CLZObject {
|
||||
public:
|
||||
#pragma pack(4)
|
||||
typedef struct SHeader {
|
||||
DWORD dwFourCC;
|
||||
DWORD dwEncryptSize; // <20><>ȣȭ<C8A3><C8AD> ũ<><C5A9>
|
||||
DWORD dwCompressedSize; // <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ũ<><C5A9>
|
||||
DWORD dwRealSize; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ũ<><C5A9>
|
||||
} THeader;
|
||||
#pragma pack()
|
||||
|
||||
CLZObject();
|
||||
~CLZObject();
|
||||
|
||||
void Clear();
|
||||
|
||||
void BeginCompress(const void *pvIn, UINT uiInLen);
|
||||
bool Compress();
|
||||
|
||||
bool BeginDecompress(const void *pvIn);
|
||||
bool Decompress(DWORD *pdwKey = NULL);
|
||||
|
||||
bool Encrypt(DWORD *pdwKey);
|
||||
BYTE *Decrypt(DWORD *pdwKey);
|
||||
|
||||
const THeader &GetHeader() { return *m_pHeader; }
|
||||
BYTE *GetBuffer() { return m_pbBuffer; }
|
||||
DWORD GetSize();
|
||||
|
||||
private:
|
||||
void Initialize();
|
||||
|
||||
BYTE *m_pbBuffer;
|
||||
DWORD m_dwBufferSize;
|
||||
|
||||
THeader *m_pHeader;
|
||||
const BYTE *m_pbIn;
|
||||
bool m_bCompressed;
|
||||
|
||||
static DWORD ms_dwFourCC;
|
||||
};
|
||||
|
||||
class CLZO : public singleton<CLZO> {
|
||||
public:
|
||||
CLZO();
|
||||
virtual ~CLZO();
|
||||
|
||||
bool CompressMemory(CLZObject &rObj, const void *pIn, UINT uiInLen);
|
||||
bool CompressEncryptedMemory(CLZObject &rObj, const void *pIn, UINT uiInLen,
|
||||
DWORD *pdwKey);
|
||||
bool Decompress(CLZObject &rObj, const BYTE *pbBuf, DWORD *pdwKey = NULL);
|
||||
BYTE *GetWorkMemory();
|
||||
|
||||
private:
|
||||
BYTE *m_pWorkMem;
|
||||
};
|
||||
|
||||
#endif
|
||||
100
src/DumpProto/tea.cpp
Normal file
100
src/DumpProto/tea.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Filename: tea.c
|
||||
* Description: TEA 암호화 모듈
|
||||
*
|
||||
* Author: 김한주 (aka. 비엽, Cronan), 송영진 (aka. myevan, 빗자루)
|
||||
*/
|
||||
#include "tea.h"
|
||||
#include <memory.h>
|
||||
|
||||
/*
|
||||
* TEA Encryption Module Instruction
|
||||
* Edited by 김한주 aka. 비엽, Cronan
|
||||
*
|
||||
* void tea_code(const unsigned long sz, const unsigned long sy, const unsigned long *key, unsigned long *dest)
|
||||
* void tea_decode(const unsigned long sz, const unsigned long sy, const unsigned long *key, unsigned long *dest)
|
||||
* 8바이트를 암호/복호화 할때 사용된다. key 는 16 바이트여야 한다.
|
||||
* sz, sy 는 8바이트의 역순으로 대입한다.
|
||||
*
|
||||
* int tea_decrypt(unsigned long *dest, const unsigned long *src, const unsigned long *key, int size);
|
||||
* int tea_encrypt(unsigned long *dest, const unsigned long *src, const unsigned long *key, int size);
|
||||
* 한꺼번에 8 바이트 이상을 암호/복호화 할때 사용한다. 만약 size 가
|
||||
* 8의 배수가 아니면 8의 배수로 크기를 "늘려서" 암호화 한다.
|
||||
*
|
||||
* ex. tea_code(pdwSrc[1], pdwSrc[0], pdwKey, pdwDest);
|
||||
* tea_decrypt(pdwDest, pdwSrc, pdwKey, nSize);
|
||||
*/
|
||||
|
||||
#define TEA_ROUND 32 // 32 를 권장하며, 높을 수록 결과가 난해해 진다.
|
||||
#define DELTA 0x9E3779B9 // DELTA 값 바꾸지 말것.
|
||||
|
||||
void tea_code(const unsigned long sz, const unsigned long sy, const unsigned long *key, unsigned long *dest)
|
||||
{
|
||||
register unsigned long y = sy, z = sz, sum = 0;
|
||||
unsigned long n = TEA_ROUND;
|
||||
|
||||
while (n-- > 0)
|
||||
{
|
||||
y += ((z << 4 ^ z >> 5) + z) ^ (sum + key[sum & 3]);
|
||||
sum += DELTA;
|
||||
z += ((y << 4 ^ y >> 5) + y) ^ (sum + key[sum >> 11 & 3]);
|
||||
}
|
||||
|
||||
*(dest++) = y;
|
||||
*dest = z;
|
||||
}
|
||||
|
||||
void tea_decode(const unsigned long sz, const unsigned long sy, const unsigned long *key, unsigned long *dest)
|
||||
{
|
||||
#pragma warning(disable:4307)
|
||||
register unsigned long y = sy, z = sz, sum = DELTA * TEA_ROUND;
|
||||
#pragma warning(default:4307)
|
||||
|
||||
unsigned long n = TEA_ROUND;
|
||||
|
||||
while (n-- > 0)
|
||||
{
|
||||
z -= ((y << 4 ^ y >> 5) + y) ^ (sum + key[sum >> 11 & 3]);
|
||||
sum -= DELTA;
|
||||
y -= ((z << 4 ^ z >> 5) + z) ^ (sum + key[sum & 3]);
|
||||
}
|
||||
|
||||
*(dest++) = y;
|
||||
*dest = z;
|
||||
}
|
||||
|
||||
int tea_encrypt(unsigned long *dest, const unsigned long *src, const unsigned long * key, int size)
|
||||
{
|
||||
int i;
|
||||
int resize;
|
||||
|
||||
if (size % 8 != 0)
|
||||
{
|
||||
resize = size + 8 - (size % 8);
|
||||
memset((char *) src + size, 0, resize - size);
|
||||
}
|
||||
else
|
||||
resize = size;
|
||||
|
||||
for (i = 0; i < resize >> 3; i++, dest += 2, src += 2)
|
||||
tea_code(*(src + 1), *src, key, dest);
|
||||
|
||||
return (resize);
|
||||
}
|
||||
|
||||
int tea_decrypt(unsigned long *dest, const unsigned long *src, const unsigned long * key, int size)
|
||||
{
|
||||
int i;
|
||||
int resize;
|
||||
|
||||
if (size % 8 != 0)
|
||||
resize = size + 8 - (size % 8);
|
||||
else
|
||||
resize = size;
|
||||
|
||||
for (i = 0; i < resize >> 3; i++, dest += 2, src += 2)
|
||||
tea_decode(*(src + 1), *src, key, dest);
|
||||
|
||||
return (resize);
|
||||
}
|
||||
|
||||
17
src/DumpProto/tea.h
Normal file
17
src/DumpProto/tea.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* TEA is a 64-bit symmetric block cipher with a 128-bit key, developed
|
||||
by David J. Wheeler and Roger M. Needham, and described in their
|
||||
paper at <URL:http://www.cl.cam.ac.uk/ftp/users/djw3/tea.ps>.
|
||||
|
||||
This implementation is based on their code in
|
||||
<URL:http://www.cl.cam.ac.uk/ftp/users/djw3/xtea.ps> */
|
||||
|
||||
int tea_encrypt(unsigned long *dest, const unsigned long *src, const unsigned long *key, int size);
|
||||
int tea_decrypt(unsigned long *dest, const unsigned long *src, const unsigned long *key, int size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
@@ -534,7 +534,7 @@ void CGraphicThingInstance::RegisterMotionThing(DWORD dwMotionKey, CGraphicThing
|
||||
{
|
||||
CGraphicThing::TRef * pMotionRef = new CGraphicThing::TRef;
|
||||
pMotionRef->SetPointer(pMotionThing);
|
||||
m_roMotionThingMap.insert(std::make_pair(dwMotionKey, pMotionRef));
|
||||
m_roMotionThingMap.insert(std::map<DWORD, CGraphicThing::TRef *>::value_type(dwMotionKey, pMotionRef));
|
||||
}
|
||||
|
||||
void CGraphicThingInstance::ResetLocalTime()
|
||||
|
||||
@@ -3,171 +3,86 @@
|
||||
#include "PackLib/PackManager.h"
|
||||
#include "FileLoaderThread.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "GameThreadPool.h"
|
||||
|
||||
CFileLoaderThread::CFileLoaderThread() : m_bShutdowned(false), m_pArg(NULL), m_hThread(NULL), m_uThreadID(0)
|
||||
CFileLoaderThread::CFileLoaderThread() : m_bShutdowned(false)
|
||||
{
|
||||
}
|
||||
|
||||
CFileLoaderThread::~CFileLoaderThread()
|
||||
{
|
||||
Destroy();
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
int CFileLoaderThread::Create(void * arg)
|
||||
bool CFileLoaderThread::Create(void * arg)
|
||||
{
|
||||
Arg(arg);
|
||||
m_hThread = (HANDLE) _beginthreadex(NULL, 0, EntryPoint, this, 0, &m_uThreadID);
|
||||
|
||||
if (!m_hThread)
|
||||
return false;
|
||||
|
||||
SetThreadPriority(m_hThread, THREAD_PRIORITY_NORMAL);
|
||||
// Modern implementation doesn't need explicit thread creation
|
||||
// The global CGameThreadPool handles threading
|
||||
m_bShutdowned = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
UINT CFileLoaderThread::Run(void * arg)
|
||||
{
|
||||
if (!Setup())
|
||||
return 0;
|
||||
|
||||
return (Execute(arg));
|
||||
}
|
||||
|
||||
/* Static */
|
||||
UINT CALLBACK CFileLoaderThread::EntryPoint(void * pThis)
|
||||
{
|
||||
CFileLoaderThread * pThread = (CFileLoaderThread *) pThis;
|
||||
return pThread->Run(pThread->Arg());
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CFileLoaderThread::Destroy()
|
||||
{
|
||||
if (m_hSemaphore)
|
||||
{
|
||||
CloseHandle(m_hSemaphore);
|
||||
m_hSemaphore = NULL;
|
||||
}
|
||||
|
||||
stl_wipe(m_pRequestDeque);
|
||||
stl_wipe(m_pCompleteDeque);
|
||||
}
|
||||
|
||||
UINT CFileLoaderThread::Setup()
|
||||
{
|
||||
m_hSemaphore = CreateSemaphore(NULL, // no security attributes
|
||||
0, // initial count
|
||||
65535, // maximum count
|
||||
NULL); // unnamed semaphore
|
||||
if (!m_hSemaphore)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void CFileLoaderThread::Shutdown()
|
||||
{
|
||||
if (!m_hSemaphore)
|
||||
return;
|
||||
|
||||
BOOL bRet;
|
||||
|
||||
m_bShutdowned = true;
|
||||
|
||||
do
|
||||
// Clear any pending completed items
|
||||
{
|
||||
bRet = ReleaseSemaphore(m_hSemaphore, 1, NULL);
|
||||
std::lock_guard<std::mutex> lock(m_CompleteMutex);
|
||||
stl_wipe(m_pCompleteDeque);
|
||||
}
|
||||
while (!bRet);
|
||||
|
||||
WaitForSingleObject(m_hThread, 10000); // 쓰레드가 종료 되기를 10초 기다림
|
||||
}
|
||||
|
||||
UINT CFileLoaderThread::Execute(void * /*pvArg*/)
|
||||
void CFileLoaderThread::Request(const std::string& c_rstFileName)
|
||||
{
|
||||
while (!m_bShutdowned)
|
||||
if (m_bShutdowned)
|
||||
return;
|
||||
|
||||
// Enqueue file loading to the global thread pool
|
||||
CGameThreadPool* pThreadPool = CGameThreadPool::InstancePtr();
|
||||
if (pThreadPool)
|
||||
{
|
||||
DWORD dwWaitResult;
|
||||
|
||||
dwWaitResult = WaitForSingleObject(m_hSemaphore, INFINITE);
|
||||
|
||||
if (m_bShutdowned)
|
||||
break;
|
||||
|
||||
switch (dwWaitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
{
|
||||
Process();
|
||||
}
|
||||
break;
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
TraceError("CFileLoaderThread::Execute: Timeout occured while time-out interval is INIFITE");
|
||||
break;
|
||||
}
|
||||
pThreadPool->Enqueue([this, c_rstFileName]()
|
||||
{
|
||||
ProcessFile(c_rstFileName);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to synchronous loading if thread pool not available
|
||||
ProcessFile(c_rstFileName);
|
||||
}
|
||||
|
||||
Destroy();
|
||||
return 1;
|
||||
}
|
||||
|
||||
void CFileLoaderThread::Request(std::string & c_rstFileName) // called in main thread
|
||||
bool CFileLoaderThread::Fetch(TData ** ppData)
|
||||
{
|
||||
TData * pData = new TData;
|
||||
|
||||
pData->File.clear();
|
||||
pData->stFileName = c_rstFileName;
|
||||
|
||||
m_RequestMutex.Lock();
|
||||
m_pRequestDeque.push_back(pData);
|
||||
m_RequestMutex.Unlock();
|
||||
|
||||
++m_iRestSemCount;
|
||||
|
||||
if (!ReleaseSemaphore(m_hSemaphore, m_iRestSemCount, NULL))
|
||||
TraceError("CFileLoaderThread::Request: ReleaseSemaphore error");
|
||||
|
||||
--m_iRestSemCount;
|
||||
}
|
||||
|
||||
bool CFileLoaderThread::Fetch(TData ** ppData) // called in main thread
|
||||
{
|
||||
m_CompleteMutex.Lock();
|
||||
std::lock_guard<std::mutex> lock(m_CompleteMutex);
|
||||
|
||||
if (m_pCompleteDeque.empty())
|
||||
{
|
||||
m_CompleteMutex.Unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
*ppData = m_pCompleteDeque.front();
|
||||
m_pCompleteDeque.pop_front();
|
||||
|
||||
m_CompleteMutex.Unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CFileLoaderThread::Process() // called in loader thread
|
||||
void CFileLoaderThread::ProcessFile(const std::string& fileName)
|
||||
{
|
||||
m_RequestMutex.Lock();
|
||||
|
||||
if (m_pRequestDeque.empty())
|
||||
{
|
||||
m_RequestMutex.Unlock();
|
||||
if (m_bShutdowned)
|
||||
return;
|
||||
}
|
||||
|
||||
TData * pData = m_pRequestDeque.front();
|
||||
m_pRequestDeque.pop_front();
|
||||
|
||||
m_RequestMutex.Unlock();
|
||||
TData * pData = new TData;
|
||||
pData->File.clear();
|
||||
pData->stFileName = fileName;
|
||||
|
||||
CPackManager::instance().GetFile(pData->stFileName, pData->File);
|
||||
|
||||
m_CompleteMutex.Lock();
|
||||
m_pCompleteDeque.push_back(pData);
|
||||
m_CompleteMutex.Unlock();
|
||||
// Add to completed queue
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_CompleteMutex);
|
||||
m_pCompleteDeque.push_back(pData);
|
||||
}
|
||||
|
||||
Sleep(g_iLoadingDelayTime);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
#define __INC_YMIR_ETERLIB_FILELOADERTHREAD_H__
|
||||
|
||||
#include <deque>
|
||||
#include "Thread.h"
|
||||
#include "Mutex.h"
|
||||
#include <mutex>
|
||||
#include "PackLib/PackManager.h"
|
||||
|
||||
class CFileLoaderThread
|
||||
class CFileLoaderThread
|
||||
{
|
||||
public:
|
||||
typedef struct SData
|
||||
@@ -19,41 +18,19 @@ class CFileLoaderThread
|
||||
CFileLoaderThread();
|
||||
~CFileLoaderThread();
|
||||
|
||||
int Create(void * arg);
|
||||
|
||||
bool Create(void * arg);
|
||||
void Shutdown();
|
||||
|
||||
public:
|
||||
void Request(std::string & c_rstFileName);
|
||||
void Request(const std::string& c_rstFileName);
|
||||
bool Fetch(TData ** ppData);
|
||||
void Shutdown();
|
||||
|
||||
protected:
|
||||
static UINT CALLBACK EntryPoint(void * pThis);
|
||||
UINT Run(void * arg);
|
||||
|
||||
void * Arg() const { return m_pArg; }
|
||||
void Arg(void * arg) { m_pArg = arg; }
|
||||
|
||||
HANDLE m_hThread;
|
||||
|
||||
private:
|
||||
void * m_pArg;
|
||||
unsigned m_uThreadID;
|
||||
|
||||
protected:
|
||||
UINT Setup();
|
||||
UINT Execute(void * pvArg);
|
||||
void Destroy();
|
||||
void Process();
|
||||
void ProcessFile(const std::string& fileName);
|
||||
|
||||
private:
|
||||
std::deque<TData *> m_pRequestDeque;
|
||||
Mutex m_RequestMutex;
|
||||
|
||||
std::deque<TData *> m_pCompleteDeque;
|
||||
Mutex m_CompleteMutex;
|
||||
|
||||
HANDLE m_hSemaphore;
|
||||
int m_iRestSemCount;
|
||||
std::deque<TData*> m_pCompleteDeque;
|
||||
std::mutex m_CompleteMutex;
|
||||
bool m_bShutdowned;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
#include "StdAfx.h"
|
||||
#include "FileLoaderThreadPool.h"
|
||||
#include "BufferPool.h"
|
||||
#include "ImageDecoder.h"
|
||||
#include "PackLib/PackManager.h"
|
||||
#include <algorithm>
|
||||
|
||||
static const bool USE_STAGED_TEXTURE_LOADING = true;
|
||||
|
||||
CFileLoaderThreadPool::CFileLoaderThreadPool()
|
||||
: m_pCompletedQueue(nullptr)
|
||||
, m_bShutdown(false)
|
||||
, m_nextRequestID(0)
|
||||
, m_activeTasks(0)
|
||||
, m_threadCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
CFileLoaderThreadPool::~CFileLoaderThreadPool()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool CFileLoaderThreadPool::Initialize(unsigned int threadCount)
|
||||
{
|
||||
if (!m_workers.empty())
|
||||
{
|
||||
TraceError("CFileLoaderThreadPool::Initialize: Already initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (threadCount == 0)
|
||||
{
|
||||
threadCount = std::thread::hardware_concurrency();
|
||||
if (threadCount == 0)
|
||||
threadCount = 4;
|
||||
else
|
||||
threadCount = std::max(4u, threadCount / 2);
|
||||
}
|
||||
|
||||
threadCount = std::max(4u, std::min(16u, threadCount));
|
||||
m_threadCount = threadCount;
|
||||
|
||||
Tracenf("CFileLoaderThreadPool: Initializing with %u worker threads", threadCount);
|
||||
|
||||
m_pCompletedQueue = new SPSCQueue<TLoadResult>(COMPLETED_QUEUE_SIZE);
|
||||
|
||||
m_workers.reserve(threadCount);
|
||||
for (unsigned int i = 0; i < threadCount; ++i)
|
||||
{
|
||||
TWorkerThread worker;
|
||||
worker.pRequestQueue = new SPSCQueue<TLoadRequest>(REQUEST_QUEUE_SIZE);
|
||||
worker.bBusy.store(false, std::memory_order_relaxed);
|
||||
|
||||
try
|
||||
{
|
||||
worker.thread = std::thread(&CFileLoaderThreadPool::WorkerThreadFunction, this, i);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
TraceError("CFileLoaderThreadPool::Initialize: Failed to create thread %u: %s", i, e.what());
|
||||
delete worker.pRequestQueue;
|
||||
worker.pRequestQueue = nullptr;
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_workers.push_back(std::move(worker));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CFileLoaderThreadPool::Shutdown()
|
||||
{
|
||||
if (m_workers.empty())
|
||||
return;
|
||||
|
||||
// Signal shutdown
|
||||
m_bShutdown.store(true, std::memory_order_release);
|
||||
|
||||
// Wait for all workers to finish
|
||||
for (auto& worker : m_workers)
|
||||
{
|
||||
if (worker.thread.joinable())
|
||||
worker.thread.join();
|
||||
|
||||
// Cleanup request queue
|
||||
if (worker.pRequestQueue)
|
||||
{
|
||||
delete worker.pRequestQueue;
|
||||
worker.pRequestQueue = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
m_workers.clear();
|
||||
|
||||
// Cleanup completed queue
|
||||
if (m_pCompletedQueue)
|
||||
{
|
||||
delete m_pCompletedQueue;
|
||||
m_pCompletedQueue = nullptr;
|
||||
}
|
||||
|
||||
m_threadCount = 0;
|
||||
}
|
||||
|
||||
bool CFileLoaderThreadPool::Request(const std::string& fileName)
|
||||
{
|
||||
if (m_workers.empty())
|
||||
{
|
||||
TraceError("CFileLoaderThreadPool::Request: Thread pool not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
TLoadRequest request;
|
||||
request.stFileName = fileName;
|
||||
request.requestID = m_nextRequestID.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
request.decodeImage = false;
|
||||
if (USE_STAGED_TEXTURE_LOADING)
|
||||
{
|
||||
size_t dotPos = fileName.find_last_of('.');
|
||||
if (dotPos != std::string::npos && dotPos + 1 < fileName.size())
|
||||
{
|
||||
const char* ext = fileName.c_str() + dotPos;
|
||||
size_t extLen = fileName.size() - dotPos;
|
||||
|
||||
if ((extLen == 4 && (_stricmp(ext, ".dds") == 0 || _stricmp(ext, ".png") == 0 ||
|
||||
_stricmp(ext, ".jpg") == 0 || _stricmp(ext, ".tga") == 0 || _stricmp(ext, ".bmp") == 0)) ||
|
||||
(extLen == 5 && _stricmp(ext, ".jpeg") == 0))
|
||||
{
|
||||
request.decodeImage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int targetWorker = SelectLeastBusyWorker();
|
||||
|
||||
if (!m_workers[targetWorker].pRequestQueue->Push(request))
|
||||
{
|
||||
for (unsigned int i = 0; i < m_threadCount; ++i)
|
||||
{
|
||||
unsigned int workerIdx = (targetWorker + i) % m_threadCount;
|
||||
if (m_workers[workerIdx].pRequestQueue->Push(request))
|
||||
{
|
||||
m_activeTasks.fetch_add(1, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
TraceError("CFileLoaderThreadPool::Request: All worker queues full for file: %s", fileName.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_activeTasks.fetch_add(1, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CFileLoaderThreadPool::Fetch(TLoadResult& result)
|
||||
{
|
||||
if (!m_pCompletedQueue)
|
||||
return false;
|
||||
|
||||
if (m_pCompletedQueue->Pop(result))
|
||||
{
|
||||
m_activeTasks.fetch_sub(1, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t CFileLoaderThreadPool::GetPendingCount() const
|
||||
{
|
||||
size_t total = 0;
|
||||
for (const auto& worker : m_workers)
|
||||
{
|
||||
if (worker.pRequestQueue)
|
||||
total += worker.pRequestQueue->Size();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
bool CFileLoaderThreadPool::IsIdle() const
|
||||
{
|
||||
return m_activeTasks.load(std::memory_order_acquire) == 0;
|
||||
}
|
||||
|
||||
unsigned int CFileLoaderThreadPool::SelectLeastBusyWorker() const
|
||||
{
|
||||
unsigned int leastBusyIdx = 0;
|
||||
size_t minSize = m_workers[0].pRequestQueue->Size();
|
||||
|
||||
for (unsigned int i = 1; i < m_threadCount; ++i)
|
||||
{
|
||||
size_t queueSize = m_workers[i].pRequestQueue->Size();
|
||||
if (queueSize < minSize)
|
||||
{
|
||||
minSize = queueSize;
|
||||
leastBusyIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
return leastBusyIdx;
|
||||
}
|
||||
|
||||
void CFileLoaderThreadPool::WorkerThreadFunction(unsigned int workerIndex)
|
||||
{
|
||||
TWorkerThread& worker = m_workers[workerIndex];
|
||||
SPSCQueue<TLoadRequest>* pRequestQueue = worker.pRequestQueue;
|
||||
|
||||
CBufferPool* pBufferPool = CPackManager::instance().GetBufferPool();
|
||||
|
||||
Tracenf("CFileLoaderThreadPool: Worker thread %u started", workerIndex);
|
||||
|
||||
int idleCount = 0;
|
||||
|
||||
while (!m_bShutdown.load(std::memory_order_acquire))
|
||||
{
|
||||
TLoadRequest request;
|
||||
|
||||
if (pRequestQueue->Pop(request))
|
||||
{
|
||||
idleCount = 0;
|
||||
worker.bBusy.store(true, std::memory_order_release);
|
||||
|
||||
TLoadResult result;
|
||||
result.stFileName = request.stFileName;
|
||||
result.requestID = request.requestID;
|
||||
result.File.clear();
|
||||
result.hasDecodedImage = false;
|
||||
|
||||
CPackManager::instance().GetFileWithPool(request.stFileName, result.File, pBufferPool);
|
||||
|
||||
if (request.decodeImage && !result.File.empty())
|
||||
{
|
||||
if (CImageDecoder::DecodeImage(result.File.data(), result.File.size(), result.decodedImage))
|
||||
{
|
||||
result.hasDecodedImage = true;
|
||||
result.File.clear();
|
||||
}
|
||||
}
|
||||
|
||||
while (!m_pCompletedQueue->Push(result))
|
||||
{
|
||||
std::this_thread::yield();
|
||||
|
||||
if (m_bShutdown.load(std::memory_order_acquire))
|
||||
break;
|
||||
}
|
||||
|
||||
worker.bBusy.store(false, std::memory_order_release);
|
||||
}
|
||||
else
|
||||
{
|
||||
idleCount++;
|
||||
if (idleCount > 1000)
|
||||
{
|
||||
Sleep(1);
|
||||
idleCount = 0;
|
||||
}
|
||||
else if (idleCount > 10)
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tracenf("CFileLoaderThreadPool: Worker thread %u stopped", workerIndex);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
#ifndef __INC_ETERLIB_FILELOADERTHREADPOOL_H__
|
||||
#define __INC_ETERLIB_FILELOADERTHREADPOOL_H__
|
||||
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include "SPSCQueue.h"
|
||||
#include "PackLib/PackManager.h"
|
||||
#include "DecodedImageData.h"
|
||||
|
||||
class CFileLoaderThreadPool
|
||||
{
|
||||
public:
|
||||
struct TLoadRequest
|
||||
{
|
||||
std::string stFileName;
|
||||
uint32_t requestID;
|
||||
bool decodeImage;
|
||||
};
|
||||
|
||||
struct TLoadResult
|
||||
{
|
||||
std::string stFileName;
|
||||
TPackFile File;
|
||||
uint32_t requestID;
|
||||
TDecodedImageData decodedImage;
|
||||
bool hasDecodedImage;
|
||||
};
|
||||
|
||||
public:
|
||||
CFileLoaderThreadPool();
|
||||
~CFileLoaderThreadPool();
|
||||
|
||||
bool Initialize(unsigned int threadCount = 0);
|
||||
void Shutdown();
|
||||
bool Request(const std::string& fileName);
|
||||
bool Fetch(TLoadResult& result);
|
||||
size_t GetPendingCount() const;
|
||||
bool IsIdle() const;
|
||||
|
||||
private:
|
||||
struct TWorkerThread
|
||||
{
|
||||
std::thread thread;
|
||||
SPSCQueue<TLoadRequest>* pRequestQueue;
|
||||
std::atomic<bool> bBusy;
|
||||
|
||||
TWorkerThread() : pRequestQueue(nullptr), bBusy(false) {}
|
||||
|
||||
TWorkerThread(TWorkerThread&& other) noexcept
|
||||
: thread(std::move(other.thread))
|
||||
, pRequestQueue(other.pRequestQueue)
|
||||
, bBusy(other.bBusy.load())
|
||||
{
|
||||
other.pRequestQueue = nullptr;
|
||||
}
|
||||
|
||||
TWorkerThread& operator=(TWorkerThread&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
thread = std::move(other.thread);
|
||||
pRequestQueue = other.pRequestQueue;
|
||||
bBusy.store(other.bBusy.load());
|
||||
other.pRequestQueue = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TWorkerThread(const TWorkerThread&) = delete;
|
||||
TWorkerThread& operator=(const TWorkerThread&) = delete;
|
||||
};
|
||||
|
||||
void WorkerThreadFunction(unsigned int workerIndex);
|
||||
unsigned int SelectLeastBusyWorker() const;
|
||||
|
||||
private:
|
||||
std::vector<TWorkerThread> m_workers;
|
||||
SPSCQueue<TLoadResult>* m_pCompletedQueue;
|
||||
|
||||
std::atomic<bool> m_bShutdown;
|
||||
std::atomic<uint32_t> m_nextRequestID;
|
||||
std::atomic<int> m_activeTasks; // Fast IsIdle check
|
||||
unsigned int m_threadCount;
|
||||
|
||||
static const size_t REQUEST_QUEUE_SIZE = 16384; // Doubled from 8192
|
||||
static const size_t COMPLETED_QUEUE_SIZE = 32768; // Doubled from 16384
|
||||
};
|
||||
|
||||
#endif // __INC_ETERLIB_FILELOADERTHREADPOOL_H__
|
||||
@@ -4,7 +4,6 @@
|
||||
CGameThreadPool::CGameThreadPool()
|
||||
: m_bShutdown(false)
|
||||
, m_bInitialized(false)
|
||||
, m_iNextWorkerIndex(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -15,7 +14,9 @@ CGameThreadPool::~CGameThreadPool()
|
||||
|
||||
void CGameThreadPool::Initialize(int iWorkerCount)
|
||||
{
|
||||
if (m_bInitialized)
|
||||
std::lock_guard<std::mutex> lock(m_lifecycleMutex);
|
||||
|
||||
if (m_bInitialized.load(std::memory_order_acquire))
|
||||
{
|
||||
TraceError("CGameThreadPool::Initialize - Already initialized!");
|
||||
return;
|
||||
@@ -35,30 +36,43 @@ void CGameThreadPool::Initialize(int iWorkerCount)
|
||||
Tracef("CGameThreadPool::Initialize - Creating %d worker threads\n", iWorkerCount);
|
||||
|
||||
m_bShutdown.store(false, std::memory_order_release);
|
||||
m_workers.clear();
|
||||
m_workers.reserve(iWorkerCount);
|
||||
|
||||
// Initialize each worker
|
||||
// First create all workers
|
||||
for (int i = 0; i < iWorkerCount; ++i)
|
||||
{
|
||||
std::unique_ptr<TWorkerThread> pWorker(new TWorkerThread());
|
||||
pWorker->pTaskQueue.reset(new SPSCQueue<TTask>(QUEUE_SIZE));
|
||||
pWorker->bBusy.store(false, std::memory_order_relaxed);
|
||||
auto pWorker = std::make_unique<TWorkerThread>();
|
||||
pWorker->pTaskQueue = std::make_unique<SPSCQueue<TTask>>(QUEUE_SIZE);
|
||||
pWorker->uTaskCount.store(0, std::memory_order_relaxed);
|
||||
pWorker->thread = std::thread(&CGameThreadPool::WorkerThreadProc, this, i);
|
||||
m_workers.push_back(std::move(pWorker));
|
||||
}
|
||||
|
||||
// Mark as initialized before starting threads
|
||||
m_bInitialized.store(true, std::memory_order_release);
|
||||
|
||||
// Then start threads after all workers are created
|
||||
for (int i = 0; i < iWorkerCount; ++i)
|
||||
{
|
||||
TWorkerThread* pWorker = m_workers[i].get();
|
||||
// Pass worker pointer directly instead of index
|
||||
pWorker->thread = std::thread(&CGameThreadPool::WorkerThreadProc, this, pWorker);
|
||||
}
|
||||
}
|
||||
|
||||
void CGameThreadPool::Destroy()
|
||||
{
|
||||
if (!m_bInitialized)
|
||||
std::lock_guard<std::mutex> lock(m_lifecycleMutex);
|
||||
|
||||
if (!m_bInitialized.load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
Tracef("CGameThreadPool::Destroy - Shutting down %d worker threads\n", GetWorkerCount());
|
||||
|
||||
// Signal shutdown first
|
||||
m_bShutdown.store(true, std::memory_order_release);
|
||||
|
||||
// Mark as not initialized to prevent new enqueues
|
||||
m_bInitialized.store(false, std::memory_order_release);
|
||||
|
||||
// Join all worker threads
|
||||
@@ -71,17 +85,23 @@ void CGameThreadPool::Destroy()
|
||||
m_workers.clear();
|
||||
}
|
||||
|
||||
void CGameThreadPool::WorkerThreadProc(int iWorkerIndex)
|
||||
void CGameThreadPool::WorkerThreadProc(TWorkerThread* pWorker)
|
||||
{
|
||||
TWorkerThread* pWorker = m_workers[iWorkerIndex].get();
|
||||
int iIdleCount = 0;
|
||||
|
||||
while (!m_bShutdown.load(std::memory_order_acquire))
|
||||
{
|
||||
TTask task;
|
||||
if (pWorker->pTaskQueue->Pop(task))
|
||||
|
||||
// Pop from queue with minimal locking
|
||||
bool bHasTask = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(pWorker->queueMutex);
|
||||
bHasTask = pWorker->pTaskQueue->Pop(task);
|
||||
}
|
||||
|
||||
if (bHasTask)
|
||||
{
|
||||
pWorker->bBusy.store(true, std::memory_order_relaxed);
|
||||
iIdleCount = 0;
|
||||
|
||||
// Execute the task
|
||||
@@ -91,15 +111,14 @@ void CGameThreadPool::WorkerThreadProc(int iWorkerIndex)
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
TraceError("CGameThreadPool::WorkerThreadProc - Exception in worker %d: %s", iWorkerIndex, e.what());
|
||||
TraceError("CGameThreadPool::WorkerThreadProc - Exception: %s", e.what());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
TraceError("CGameThreadPool::WorkerThreadProc - Unknown exception in worker %d", iWorkerIndex);
|
||||
TraceError("CGameThreadPool::WorkerThreadProc - Unknown exception");
|
||||
}
|
||||
|
||||
pWorker->uTaskCount.fetch_sub(1, std::memory_order_relaxed);
|
||||
pWorker->bBusy.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -120,9 +139,41 @@ void CGameThreadPool::WorkerThreadProc(int iWorkerIndex)
|
||||
{
|
||||
// Longer sleep for extended idle
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
// Reset idle count to prevent overflow
|
||||
if (iIdleCount > 10000)
|
||||
iIdleCount = 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process remaining tasks before shutdown
|
||||
TTask task;
|
||||
while (true)
|
||||
{
|
||||
bool bHasTask = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(pWorker->queueMutex);
|
||||
bHasTask = pWorker->pTaskQueue->Pop(task);
|
||||
}
|
||||
|
||||
if (!bHasTask)
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
task();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
TraceError("CGameThreadPool::WorkerThreadProc - Exception during shutdown: %s", e.what());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
TraceError("CGameThreadPool::WorkerThreadProc - Unknown exception during shutdown");
|
||||
}
|
||||
|
||||
pWorker->uTaskCount.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
int CGameThreadPool::SelectLeastBusyWorker() const
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <future>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
class CGameThreadPool : public CSingleton<CGameThreadPool>
|
||||
{
|
||||
@@ -35,30 +36,29 @@ public:
|
||||
size_t GetPendingTaskCount() const;
|
||||
|
||||
// Check if pool is initialized
|
||||
bool IsInitialized() const { return m_bInitialized; }
|
||||
bool IsInitialized() const { return m_bInitialized.load(std::memory_order_acquire); }
|
||||
|
||||
private:
|
||||
struct TWorkerThread
|
||||
{
|
||||
std::thread thread;
|
||||
std::unique_ptr<SPSCQueue<TTask>> pTaskQueue;
|
||||
std::atomic<bool> bBusy;
|
||||
std::mutex queueMutex; // Mutex to protect SPSC queue from multiple producers
|
||||
std::atomic<uint32_t> uTaskCount;
|
||||
|
||||
TWorkerThread()
|
||||
: bBusy(false)
|
||||
, uTaskCount(0)
|
||||
: uTaskCount(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void WorkerThreadProc(int iWorkerIndex);
|
||||
void WorkerThreadProc(TWorkerThread* pWorker);
|
||||
int SelectLeastBusyWorker() const;
|
||||
|
||||
std::vector<std::unique_ptr<TWorkerThread>> m_workers;
|
||||
std::atomic<bool> m_bShutdown;
|
||||
std::atomic<bool> m_bInitialized;
|
||||
std::atomic<int> m_iNextWorkerIndex; // For round-robin distribution
|
||||
mutable std::mutex m_lifecycleMutex; // Protects initialization/destruction
|
||||
|
||||
static const size_t QUEUE_SIZE = 8192;
|
||||
};
|
||||
@@ -67,9 +67,14 @@ private:
|
||||
template<typename TFunc>
|
||||
std::future<void> CGameThreadPool::Enqueue(TFunc&& func)
|
||||
{
|
||||
if (!m_bInitialized)
|
||||
// Lock to ensure thread pool isn't being destroyed
|
||||
std::unique_lock<std::mutex> lock(m_lifecycleMutex);
|
||||
|
||||
if (!m_bInitialized.load(std::memory_order_acquire))
|
||||
{
|
||||
// If not initialized, execute on calling thread
|
||||
lock.unlock(); // No need to hold lock
|
||||
|
||||
auto promise = std::make_shared<std::promise<void>>();
|
||||
auto future = promise->get_future();
|
||||
try
|
||||
@@ -109,10 +114,24 @@ std::future<void> CGameThreadPool::Enqueue(TFunc&& func)
|
||||
int iWorkerIndex = SelectLeastBusyWorker();
|
||||
TWorkerThread* pWorker = m_workers[iWorkerIndex].get();
|
||||
|
||||
// Try to enqueue the task
|
||||
if (!pWorker->pTaskQueue->Push(std::move(task)))
|
||||
// Increment task count before pushing
|
||||
pWorker->uTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
// Try to enqueue the task with mutex protection for SPSC queue
|
||||
bool bPushed = false;
|
||||
{
|
||||
// Queue is full, execute on calling thread as fallback
|
||||
std::lock_guard<std::mutex> queueLock(pWorker->queueMutex);
|
||||
bPushed = pWorker->pTaskQueue->Push(std::move(task));
|
||||
}
|
||||
|
||||
if (!bPushed)
|
||||
{
|
||||
// Queue is full, decrement count and execute on calling thread as fallback
|
||||
pWorker->uTaskCount.fetch_sub(1, std::memory_order_relaxed);
|
||||
|
||||
// Release lifecycle lock before executing task
|
||||
lock.unlock();
|
||||
|
||||
try
|
||||
{
|
||||
(*pFunc)();
|
||||
@@ -123,10 +142,6 @@ std::future<void> CGameThreadPool::Enqueue(TFunc&& func)
|
||||
promise->set_exception(std::current_exception());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pWorker->uTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@@ -71,14 +71,7 @@ void CResourceManager::ProcessBackgroundLoading()
|
||||
|
||||
//printf("REQ %s\n", stFileName.c_str());
|
||||
|
||||
if (m_pLoaderThreadPool)
|
||||
{
|
||||
m_pLoaderThreadPool->Request(stFileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
ms_loadingThread.Request(stFileName);
|
||||
}
|
||||
ms_loadingThread.Request(stFileName);
|
||||
|
||||
m_WaitingMap.insert(TResourceRequestMap::value_type(dwFileCRC, stFileName));
|
||||
itor = m_RequestMap.erase(itor);
|
||||
@@ -87,44 +80,7 @@ void CResourceManager::ProcessBackgroundLoading()
|
||||
|
||||
DWORD dwCurrentTime = ELTimer_GetMSec();
|
||||
|
||||
if (m_pLoaderThreadPool)
|
||||
{
|
||||
CFileLoaderThreadPool::TLoadResult result;
|
||||
while (m_pLoaderThreadPool->Fetch(result))
|
||||
{
|
||||
CResource * pResource = GetResourcePointer(result.stFileName.c_str());
|
||||
|
||||
if (pResource)
|
||||
{
|
||||
if (pResource->IsEmpty())
|
||||
{
|
||||
if (result.hasDecodedImage)
|
||||
{
|
||||
CGraphicImage* pImage = dynamic_cast<CGraphicImage*>(pResource);
|
||||
if (pImage)
|
||||
{
|
||||
pImage->OnLoadFromDecodedData(result.decodedImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
pResource->OnLoad(result.File.size(), result.File.data());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pResource->OnLoad(result.File.size(), result.File.data());
|
||||
}
|
||||
|
||||
pResource->AddReferenceOnly();
|
||||
m_pResRefDecreaseWaitingMap.insert(TResourceRefDecreaseWaitingMap::value_type(dwCurrentTime, pResource));
|
||||
}
|
||||
}
|
||||
|
||||
m_WaitingMap.erase(GetCRC32(result.stFileName.c_str(), result.stFileName.size()));
|
||||
}
|
||||
}
|
||||
|
||||
// Process old thread results
|
||||
// Process thread results
|
||||
CFileLoaderThread::TData * pData;
|
||||
while (ms_loadingThread.Fetch(&pData))
|
||||
{
|
||||
@@ -580,19 +536,9 @@ void CResourceManager::ReserveDeletingResource(CResource * pResource)
|
||||
}
|
||||
|
||||
CResourceManager::CResourceManager()
|
||||
: m_pLoaderThreadPool(nullptr)
|
||||
, m_pTextureCache(nullptr)
|
||||
: m_pTextureCache(nullptr)
|
||||
{
|
||||
ms_loadingThread.Create(0);
|
||||
|
||||
m_pLoaderThreadPool = new CFileLoaderThreadPool();
|
||||
if (!m_pLoaderThreadPool->Initialize())
|
||||
{
|
||||
TraceError("CResourceManager: Failed to initialize FileLoaderThreadPool");
|
||||
delete m_pLoaderThreadPool;
|
||||
m_pLoaderThreadPool = nullptr;
|
||||
}
|
||||
|
||||
m_pTextureCache = new CTextureCache(512);
|
||||
}
|
||||
|
||||
@@ -601,12 +547,6 @@ CResourceManager::~CResourceManager()
|
||||
Destroy();
|
||||
ms_loadingThread.Shutdown();
|
||||
|
||||
if (m_pLoaderThreadPool)
|
||||
{
|
||||
delete m_pLoaderThreadPool;
|
||||
m_pLoaderThreadPool = nullptr;
|
||||
}
|
||||
|
||||
if (m_pTextureCache)
|
||||
{
|
||||
delete m_pTextureCache;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Resource.h"
|
||||
#include "FileLoaderThread.h"
|
||||
#include "FileLoaderThreadPool.h"
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
@@ -47,7 +46,6 @@ class CResourceManager : public CSingleton<CResourceManager>
|
||||
void PushBackgroundLoadingSet(std::set<std::string> & LoadingSet);
|
||||
|
||||
CTextureCache* GetTextureCache() { return m_pTextureCache; }
|
||||
CFileLoaderThreadPool* GetLoaderThreadPool() { return m_pLoaderThreadPool; }
|
||||
|
||||
protected:
|
||||
void __DestroyDeletingResourceMap();
|
||||
@@ -75,7 +73,6 @@ class CResourceManager : public CSingleton<CResourceManager>
|
||||
TResourceRefDecreaseWaitingMap m_pResRefDecreaseWaitingMap;
|
||||
|
||||
static CFileLoaderThread ms_loadingThread;
|
||||
CFileLoaderThreadPool* m_pLoaderThreadPool;
|
||||
CTextureCache* m_pTextureCache;
|
||||
|
||||
mutable std::mutex m_ResourceMapMutex; // Thread-safe resource map access
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
bool CRaceManager::s_bPreloaded = false;
|
||||
|
||||
bool __IsGuildRace(unsigned race)
|
||||
{
|
||||
if (race >= 14000 && race < 15000)
|
||||
@@ -491,173 +489,3 @@ CRaceManager::~CRaceManager()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void CRaceManager::PreloadPlayerRaceMotions()
|
||||
{
|
||||
if (s_bPreloaded)
|
||||
return;
|
||||
|
||||
CRaceManager& rkRaceMgr = CRaceManager::Instance();
|
||||
|
||||
for (DWORD dwRace = 0; dwRace <= 7; ++dwRace)
|
||||
{
|
||||
TRaceDataIterator it = rkRaceMgr.m_RaceDataMap.find(dwRace);
|
||||
if (it == rkRaceMgr.m_RaceDataMap.end())
|
||||
{
|
||||
CRaceData* pRaceData = rkRaceMgr.__LoadRaceData(dwRace);
|
||||
if (pRaceData)
|
||||
{
|
||||
rkRaceMgr.m_RaceDataMap.insert(TRaceDataMap::value_type(pRaceData->GetRaceIndex(), pRaceData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::set<CGraphicThing*> uniqueMotions;
|
||||
|
||||
for (DWORD dwRace = 0; dwRace <= 7; ++dwRace)
|
||||
{
|
||||
CRaceData* pRaceData = NULL;
|
||||
TRaceDataIterator it = rkRaceMgr.m_RaceDataMap.find(dwRace);
|
||||
if (it != rkRaceMgr.m_RaceDataMap.end())
|
||||
pRaceData = it->second;
|
||||
|
||||
if (!pRaceData)
|
||||
continue;
|
||||
|
||||
CRaceData::TMotionModeDataIterator itor;
|
||||
if (pRaceData->CreateMotionModeIterator(itor))
|
||||
{
|
||||
do
|
||||
{
|
||||
CRaceData::TMotionModeData* pMotionModeData = itor->second;
|
||||
for (auto& itorMotion : pMotionModeData->MotionVectorMap)
|
||||
{
|
||||
const CRaceData::TMotionVector& c_rMotionVector = itorMotion.second;
|
||||
for (const auto& motion : c_rMotionVector)
|
||||
{
|
||||
if (motion.pMotion)
|
||||
uniqueMotions.insert(motion.pMotion);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (pRaceData->NextMotionModeIterator(itor));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CGraphicThing*> motionVec(uniqueMotions.begin(), uniqueMotions.end());
|
||||
size_t total = motionVec.size();
|
||||
|
||||
if (total > 0)
|
||||
{
|
||||
CGameThreadPool* pThreadPool = CGameThreadPool::InstancePtr();
|
||||
if (pThreadPool && pThreadPool->IsInitialized())
|
||||
{
|
||||
size_t workerCount = pThreadPool->GetWorkerCount();
|
||||
size_t chunkSize = (total + workerCount - 1) / workerCount;
|
||||
|
||||
std::vector<std::future<void>> futures;
|
||||
futures.reserve(workerCount);
|
||||
|
||||
for (size_t i = 0; i < workerCount; ++i)
|
||||
{
|
||||
size_t start = i * chunkSize;
|
||||
size_t end = std::min(start + chunkSize, total);
|
||||
|
||||
if (start < end)
|
||||
{
|
||||
// Copy values instead of capturing by reference
|
||||
futures.push_back(pThreadPool->Enqueue([start, end, motionVec]() {
|
||||
for (size_t k = start; k < end; ++k)
|
||||
{
|
||||
motionVec[k]->AddReference();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all tasks to complete
|
||||
for (auto& f : futures)
|
||||
{
|
||||
f.wait();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to sequential if thread pool not available
|
||||
for (auto* pMotion : motionVec)
|
||||
{
|
||||
pMotion->AddReference();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s_bPreloaded = true;
|
||||
}
|
||||
|
||||
void CRaceManager::RequestAsyncRaceLoad(DWORD dwRaceIndex)
|
||||
{
|
||||
// Mark as loading
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_LoadingRacesMutex);
|
||||
if (m_LoadingRaces.find(dwRaceIndex) != m_LoadingRaces.end())
|
||||
{
|
||||
// Already loading
|
||||
return;
|
||||
}
|
||||
m_LoadingRaces.insert(dwRaceIndex);
|
||||
}
|
||||
|
||||
// Enqueue async load to game thread pool
|
||||
CGameThreadPool* pThreadPool = CGameThreadPool::InstancePtr();
|
||||
if (pThreadPool)
|
||||
{
|
||||
pThreadPool->Enqueue([this, dwRaceIndex]()
|
||||
{
|
||||
CRaceData* pRaceData = __LoadRaceData(dwRaceIndex);
|
||||
|
||||
if (pRaceData)
|
||||
{
|
||||
// Thread-safe insertion
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_RaceDataMapMutex);
|
||||
m_RaceDataMap.insert(TRaceDataMap::value_type(dwRaceIndex, pRaceData));
|
||||
}
|
||||
|
||||
Tracef("CRaceManager::RequestAsyncRaceLoad: Successfully loaded race %lu asynchronously\n", dwRaceIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceError("CRaceManager::RequestAsyncRaceLoad: Failed to load race %lu", dwRaceIndex);
|
||||
}
|
||||
|
||||
// Remove from loading set
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_LoadingRacesMutex);
|
||||
m_LoadingRaces.erase(dwRaceIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to synchronous loading if thread pool not available
|
||||
CRaceData* pRaceData = __LoadRaceData(dwRaceIndex);
|
||||
|
||||
if (pRaceData)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_RaceDataMapMutex);
|
||||
m_RaceDataMap.insert(TRaceDataMap::value_type(dwRaceIndex, pRaceData));
|
||||
}
|
||||
|
||||
// Remove from loading set
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_LoadingRacesMutex);
|
||||
m_LoadingRaces.erase(dwRaceIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CRaceManager::IsRaceLoading(DWORD dwRaceIndex) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_LoadingRacesMutex);
|
||||
return m_LoadingRaces.find(dwRaceIndex) != m_LoadingRaces.end();
|
||||
}
|
||||
|
||||
@@ -31,14 +31,6 @@ class CRaceManager : public CSingleton<CRaceManager>
|
||||
|
||||
BOOL GetRaceDataPointer(DWORD dwRaceIndex, CRaceData ** ppRaceData);
|
||||
|
||||
// Async race loading
|
||||
void RequestAsyncRaceLoad(DWORD dwRaceIndex);
|
||||
bool IsRaceLoading(DWORD dwRaceIndex) const;
|
||||
|
||||
// Race motion preloading
|
||||
static void PreloadPlayerRaceMotions();
|
||||
static bool IsPreloaded() { return s_bPreloaded; }
|
||||
|
||||
protected:
|
||||
CRaceData* __LoadRaceData(DWORD dwRaceIndex);
|
||||
bool __LoadRaceMotionList(CRaceData& rkRaceData, const char* pathName, const char* motionListFileName);
|
||||
@@ -59,5 +51,4 @@ class CRaceManager : public CSingleton<CRaceManager>
|
||||
private:
|
||||
std::string m_strPathName;
|
||||
CRaceData * m_pSelectedRaceData;
|
||||
static bool s_bPreloaded;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -713,12 +713,6 @@ PyObject * chrmgrIsPossibleEmoticon(PyObject* poSelf, PyObject* poArgs)
|
||||
return Py_BuildValue("i", result);
|
||||
}
|
||||
|
||||
PyObject * chrmgrPreloadRaceMotions(PyObject* poSelf, PyObject* poArgs)
|
||||
{
|
||||
CRaceManager::PreloadPlayerRaceMotions();
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
void initchrmgr()
|
||||
{
|
||||
static PyMethodDef s_methods[] =
|
||||
@@ -752,7 +746,6 @@ void initchrmgr()
|
||||
{ "SetAffect", chrmgrSetAffect, METH_VARARGS },
|
||||
{ "SetEmoticon", chrmgrSetEmoticon, METH_VARARGS },
|
||||
{ "IsPossibleEmoticon", chrmgrIsPossibleEmoticon, METH_VARARGS },
|
||||
{ "PreloadRaceMotions", chrmgrPreloadRaceMotions, METH_VARARGS },
|
||||
{ "RegisterEffect", chrmgrRegisterEffect, METH_VARARGS },
|
||||
{ "RegisterCacheEffect", chrmgrRegisterCacheEffect, METH_VARARGS },
|
||||
{ "RegisterPointEffect", chrmgrRegisterPointEffect, METH_VARARGS },
|
||||
|
||||
@@ -96,7 +96,7 @@ PyObject* sndSetMusicVolume(PyObject* poSelf, PyObject* poArgs)
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
PyObject* sndSetSoundVolumef(PyObject* poSelf, PyObject* poArgs)
|
||||
PyObject* sndSetSoundVolume(PyObject* poSelf, PyObject* poArgs)
|
||||
{
|
||||
float fVolume;
|
||||
if (!PyTuple_GetFloat(poArgs, 0, &fVolume))
|
||||
@@ -106,16 +106,6 @@ PyObject* sndSetSoundVolumef(PyObject* poSelf, PyObject* poArgs)
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
PyObject* sndSetSoundVolume(PyObject* poSelf, PyObject* poArgs)
|
||||
{
|
||||
float volume;
|
||||
if (!PyTuple_GetFloat(poArgs, 0, &volume))
|
||||
return Py_BuildException();
|
||||
|
||||
SoundEngine::Instance().SetSoundVolume(volume / 100.0f);
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
void initsnd()
|
||||
{
|
||||
static PyMethodDef s_methods[] =
|
||||
@@ -130,7 +120,6 @@ void initsnd()
|
||||
|
||||
{ "SetMasterVolume", sndSetMasterVolume, METH_VARARGS },
|
||||
{ "SetMusicVolume", sndSetMusicVolume, METH_VARARGS },
|
||||
{ "SetSoundVolumef", sndSetSoundVolumef, METH_VARARGS },
|
||||
{ "SetSoundVolume", sndSetSoundVolume, METH_VARARGS },
|
||||
{ NULL, NULL, NULL },
|
||||
};
|
||||
|
||||
@@ -213,7 +213,7 @@ float CPythonSystem::GetMusicVolume()
|
||||
return m_Config.music_volume;
|
||||
}
|
||||
|
||||
int CPythonSystem::GetSoundVolume()
|
||||
float CPythonSystem::GetSoundVolume()
|
||||
{
|
||||
return m_Config.voice_volume;
|
||||
}
|
||||
@@ -223,9 +223,9 @@ void CPythonSystem::SetMusicVolume(float fVolume)
|
||||
m_Config.music_volume = fVolume;
|
||||
}
|
||||
|
||||
void CPythonSystem::SetSoundVolumef(float fVolume)
|
||||
void CPythonSystem::SetSoundVolume(float fVolume)
|
||||
{
|
||||
m_Config.voice_volume = int(5 * fVolume);
|
||||
m_Config.voice_volume = fVolume;
|
||||
}
|
||||
|
||||
int CPythonSystem::GetDistance()
|
||||
@@ -294,7 +294,7 @@ void CPythonSystem::SetDefaultConfig()
|
||||
|
||||
m_Config.gamma = 3;
|
||||
m_Config.music_volume = 1.0f;
|
||||
m_Config.voice_volume = 5;
|
||||
m_Config.voice_volume = 1.0f;
|
||||
|
||||
m_Config.bDecompressDDS = 0;
|
||||
m_Config.bSoftwareTiling = 0;
|
||||
@@ -409,15 +409,10 @@ bool CPythonSystem::LoadConfig()
|
||||
m_Config.is_object_culling = atoi(value) ? true : false;
|
||||
else if (!stricmp(command, "VISIBILITY"))
|
||||
m_Config.iDistance = atoi(value);
|
||||
else if (!stricmp(command, "MUSIC_VOLUME")) {
|
||||
if(strchr(value, '.') == 0) { // Old compatiability
|
||||
m_Config.music_volume = pow(10.0f, (-1.0f + (((float) atoi(value)) / 5.0f)));
|
||||
if(atoi(value) == 0)
|
||||
m_Config.music_volume = 0.0f;
|
||||
} else
|
||||
m_Config.music_volume = atof(value);
|
||||
} else if (!stricmp(command, "VOICE_VOLUME"))
|
||||
m_Config.voice_volume = (char) atoi(value);
|
||||
else if (!stricmp(command, "MUSIC_VOLUME"))
|
||||
m_Config.music_volume = atof(value);
|
||||
else if (!stricmp(command, "VOICE_VOLUME"))
|
||||
m_Config.voice_volume = atof(value);
|
||||
else if (!stricmp(command, "GAMMA"))
|
||||
m_Config.gamma = atoi(value);
|
||||
else if (!stricmp(command, "IS_SAVE_ID"))
|
||||
@@ -503,7 +498,7 @@ bool CPythonSystem::SaveConfig()
|
||||
"OBJECT_CULLING %d\n"
|
||||
"VISIBILITY %d\n"
|
||||
"MUSIC_VOLUME %.3f\n"
|
||||
"VOICE_VOLUME %d\n"
|
||||
"VOICE_VOLUME %.3f\n"
|
||||
"GAMMA %d\n"
|
||||
"IS_SAVE_ID %d\n"
|
||||
"SAVE_ID %s\n"
|
||||
|
||||
@@ -59,7 +59,7 @@ class CPythonSystem : public CSingleton<CPythonSystem>
|
||||
int iShadowLevel;
|
||||
|
||||
FLOAT music_volume;
|
||||
BYTE voice_volume;
|
||||
FLOAT voice_volume;
|
||||
|
||||
int gamma;
|
||||
|
||||
@@ -141,9 +141,9 @@ class CPythonSystem : public CSingleton<CPythonSystem>
|
||||
|
||||
// Sound
|
||||
float GetMusicVolume();
|
||||
int GetSoundVolume();
|
||||
float GetSoundVolume();
|
||||
void SetMusicVolume(float fVolume);
|
||||
void SetSoundVolumef(float fVolume);
|
||||
void SetSoundVolume(float fVolume);
|
||||
|
||||
int GetDistance();
|
||||
int GetShadowLevel();
|
||||
|
||||
@@ -134,7 +134,7 @@ PyObject * systemGetMusicVolume(PyObject * poSelf, PyObject * poArgs)
|
||||
|
||||
PyObject * systemGetSoundVolume(PyObject * poSelf, PyObject * poArgs)
|
||||
{
|
||||
return Py_BuildValue("i", CPythonSystem::Instance().GetSoundVolume());
|
||||
return Py_BuildValue("f", CPythonSystem::Instance().GetSoundVolume());
|
||||
}
|
||||
|
||||
PyObject * systemSetMusicVolume(PyObject * poSelf, PyObject * poArgs)
|
||||
@@ -147,13 +147,13 @@ PyObject * systemSetMusicVolume(PyObject * poSelf, PyObject * poArgs)
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
PyObject * systemSetSoundVolumef(PyObject * poSelf, PyObject * poArgs)
|
||||
PyObject * systemSetSoundVolume(PyObject * poSelf, PyObject * poArgs)
|
||||
{
|
||||
float fVolume;
|
||||
if (!PyTuple_GetFloat(poArgs, 0, &fVolume))
|
||||
return Py_BuildException();
|
||||
|
||||
CPythonSystem::Instance().SetSoundVolumef(fVolume);
|
||||
CPythonSystem::Instance().SetSoundVolume(fVolume);
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ void initsystem()
|
||||
{ "GetSoundVolume", systemGetSoundVolume, METH_VARARGS },
|
||||
|
||||
{ "SetMusicVolume", systemSetMusicVolume, METH_VARARGS },
|
||||
{ "SetSoundVolumef", systemSetSoundVolumef, METH_VARARGS },
|
||||
{ "SetSoundVolume", systemSetSoundVolume, METH_VARARGS },
|
||||
{ "IsSoftwareCursor", systemIsSoftwareCursor, METH_VARARGS },
|
||||
|
||||
{ "SetViewChatFlag", systemSetViewChatFlag, METH_VARARGS },
|
||||
|
||||
Reference in New Issue
Block a user