1
0

feat: finish BMapInspector framework

This commit is contained in:
2026-02-02 14:17:31 +08:00
parent def46d1b8f
commit ebbea473a4
9 changed files with 319 additions and 95 deletions

View File

@@ -193,7 +193,7 @@ PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 50
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 300
PointerAlignment: Right
PointerAlignment: Left
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer

View File

@@ -1,7 +1,8 @@
#include "Utils.hpp"
#include "Cli.hpp"
#include "Reporter.hpp"
#include "Ruleset.hpp"
#include "Cli.hpp"
#include "Map.hpp"
#include "Rule.hpp"
#include <VTAll.hpp>
#include <yycc.hpp>
#include <yycc/carton/termcolor.hpp>
@@ -24,7 +25,7 @@ static void PrintSplash() {
static std::optional<BMapInspector::Cli::Args> AcceptArgs() {
auto request = BMapInspector::Cli::parse();
if (request.has_value()) {
return request.value();
return std::move(request.value());
} else {
using BMapInspector::Cli::Error;
@@ -62,23 +63,51 @@ static std::optional<BMapInspector::Cli::Args> AcceptArgs() {
}
}
static std::optional<BMapInspector::Ruleset::RuleContext> LoadVirtools(BMapInspector::Cli::Args& args) {
return std::nullopt;
static std::optional<BMapInspector::Map::Level> LoadLevel(BMapInspector::Cli::Args& args) {
auto level = BMapInspector::Map::load(args);
if (level.has_value()) {
return std::move(level.value());
} else {
using BMapInspector::Map::Error;
std::u8string err_words;
switch (level.error()) {
case Error::BadTempDir:
err_words = u8"Can not set temporary directory for loading.";
break;
case Error::BadBallance:
err_words = u8"Can not find Ballance texture directory.";
break;
case Error::BadEncoding:
err_words = u8"Can not set encoding with your given name.";
break;
case Error::BadMap:
err_words = u8"Can not load your given map file.";
break;
default:
err_words = u8"Unknown error.";
break;
}
termcolor::cprintln(err_words, Color::Red);
termcolor::cprintln(u8"Please carefully check your map file and parameters for loading this map file.", Color::Red);
return std::nullopt;
}
}
static void CheckRules(BMapInspector::Ruleset::RuleContext& ctx) {
static void CheckRules(BMapInspector::Cli::Args& args, BMapInspector::Map::Level& level) {
// Create reporter
BMapInspector::Reporter::Reporter reporter;
// Get rule collection
BMapInspector::Ruleset::RuleCollection rule_collection;
BMapInspector::Rule::Ruleset ruleset;
// Show rule infos
std::cout << strop::printf(u8"Total %" PRIuSIZET " rule(s) are loaded.", rule_collection.GetRuleCount()) << std::endl
std::cout << strop::printf(u8"Total %" PRIuSIZET " rule(s) are loaded.", ruleset.GetRuleCount()) << std::endl
<< u8"Check may take few minutes. Please do not close this console..." << std::endl;
// Check rules one by one
for (auto* rule : rule_collection.GetRules()) {
rule->Check(reporter, ctx);
for (auto* rule : ruleset.GetRules()) {
rule->Check(reporter, level);
}
// Show report conclusion
@@ -91,6 +120,9 @@ static void CheckRules(BMapInspector::Ruleset::RuleContext& ctx) {
// Print report in detail
using BMapInspector::Utils::ReportLevel;
for (const auto& report : reporter.GetReports()) {
// Filter report first
if (!BMapInspector::Utils::FilterReportLevel(report.level, args.level)) continue;
// Okey, output this report.
switch (report.level) {
case ReportLevel::Error:
termcolor::cprintln(strop::printf(u8"[ERROR] [RULE: %s] %s", report.rule.c_str(), report.content.c_str()), Color::Red);
@@ -111,9 +143,9 @@ int main(int argc, char* argv[]) {
PrintSplash();
std::cout << std::endl;
auto ctx = LoadVirtools(args.value());
if (ctx.has_value()) {
CheckRules(ctx.value());
auto level = LoadLevel(args.value());
if (level.has_value()) {
CheckRules(args.value(), level.value());
}
}
return 0;

View File

@@ -7,7 +7,8 @@ PRIVATE
Utils.cpp
Reporter.cpp
Cli.cpp
Ruleset.cpp
Map.cpp
Rule.cpp
)
# Setup headers
target_sources(BMapInspector
@@ -17,7 +18,8 @@ FILES
Utils.hpp
Reporter.hpp
Cli.hpp
Ruleset.hpp
Map.hpp
Rule.hpp
)
# Setup header infomation
target_include_directories(BMapInspector

View File

@@ -1,6 +1,5 @@
#pragma once
#include "Utils.hpp"
#include "Reporter.hpp"
#include <yycc.hpp>
#include <yycc/macro/class_copy_move.hpp>
#include <string>
@@ -17,13 +16,13 @@ namespace BMapInspector::Cli {
};
enum class Error {
BadParse, ///< Error occurs when executing parser.
NoFile, ///< User do not specify file path for loading.
BadFile, ///< User specified file path is bad.
NoBallance, ///< User do not specify Ballance directory for loading.
BadParse, ///< Error occurs when executing parser.
NoFile, ///< User do not specify file path for loading.
BadFile, ///< User specified file path is bad.
NoBallance, ///< User do not specify Ballance directory for loading.
BadBallance, ///< User specified Ballance directory is bad.
BadEncoding, ///< User given encoding value is bad.
BadLevel, ///< User given level name is bad.
BadLevel, ///< User given level name is bad.
};
template<typename T>

View File

@@ -0,0 +1,158 @@
#include "Map.hpp"
#include <VTAll.hpp>
#include <filesystem>
#include <stdexcept>
namespace C = LibCmo::CK2;
namespace O = LibCmo::CK2::ObjImpls;
namespace BMapInspector::Map {
#pragma region Level
Level::Level(const Cli::Args& args) : m_Context(nullptr), m_LoadStatus() {
// Create contexy
this->m_Context = new C::CKContext();
// Callback for eat all output.
this->m_Context->SetOutputCallback([](LibCmo::CKSTRING strl) -> void {});
// Set temp folder
auto pm = m_Context->GetPathManager();
auto temp_dir = std::filesystem::temp_directory_path();
auto our_temp_dir = temp_dir / u8"88797AB5-915E-39AD-4800-BF1B0F59E207";
std::filesystem::create_directories(our_temp_dir);
if (!pm->SetTempFolder(our_temp_dir.u8string().c_str())) {
this->m_LoadStatus = std::unexpected(Error::BadTempDir);
return;
}
// Set ballance textures directory
auto ballance_dir = std::filesystem::path(args.ballance_path);
auto textures_dir = ballance_dir / u8"Textures";
if (!pm->AddPath(textures_dir.u8string().c_str())) {
this->m_LoadStatus = std::unexpected(Error::BadBallance);
return;
}
// Set encoding
LibCmo::XContainer::XArray<LibCmo::XContainer::XString> encs;
encs.emplace_back(args.encoding);
this->m_Context->SetEncoding(encs);
if (!this->m_Context->IsValidEncoding()) {
this->m_LoadStatus = std::unexpected(Error::BadEncoding);
return;
}
// set default texture save mode is external
this->m_Context->SetGlobalImagesSaveOptions(C::CK_TEXTURE_SAVEOPTIONS::CKTEXTURE_EXTERNAL);
// set default file write mode is whole compressed
this->m_Context->SetFileWriteMode(C::CK_FILE_WRITEMODE::CKFILE_WHOLECOMPRESSED);
// Create temp ckfile and load
C::CKFileReader reader(m_Context);
auto err = reader.DeepLoad(args.file_path.c_str());
if (err != C::CKERROR::CKERR_OK) {
// failed. clear document and return
this->m_Context->ClearAll();
this->m_LoadStatus = std::unexpected(Error::BadMap);
return;
}
// Fill our list
for (const auto& fileobj : reader.GetFileObjects()) {
auto ptr = fileobj.ObjPtr;
if (ptr == nullptr) continue;
switch (fileobj.ObjectCid) {
case LibCmo::CK2::CK_CLASSID::CKCID_GROUP:
m_ObjGroups.emplace_back(static_cast<O::CKGroup*>(ptr));
break;
case LibCmo::CK2::CK_CLASSID::CKCID_3DOBJECT:
m_Obj3dObjects.emplace_back(static_cast<O::CK3dObject*>(ptr));
break;
case LibCmo::CK2::CK_CLASSID::CKCID_MESH:
m_ObjMeshes.emplace_back(static_cast<O::CKMesh*>(ptr));
break;
case LibCmo::CK2::CK_CLASSID::CKCID_MATERIAL:
m_ObjMaterials.emplace_back(static_cast<O::CKMaterial*>(ptr));
break;
case LibCmo::CK2::CK_CLASSID::CKCID_TEXTURE:
m_ObjTextures.emplace_back(static_cast<O::CKTexture*>(ptr));
break;
case LibCmo::CK2::CK_CLASSID::CKCID_TARGETLIGHT:
m_ObjTargetLights.emplace_back(static_cast<O::CKTargetLight*>(ptr));
break;
default:
break; // skip unknow objects
}
}
// Okey
this->m_LoadStatus = {};
}
Level::~Level() {
if (this->m_Context != nullptr) {
delete this->m_Context;
}
}
YYCC_IMPL_MOVE_CTOR(Level, rhs) : m_Context(rhs.m_Context) {
rhs.m_Context = nullptr;
}
YYCC_IMPL_MOVE_OPER(Level, rhs) {
this->m_Context = rhs.m_Context;
rhs.m_Context = nullptr;
return *this;
}
#define CHECK_STATUS(this) \
if (!this->m_LoadStatus.has_value()) throw std::logic_error("can not fetch anything from not loaded level");
Result<void> Level::GetLoadStatus() const {
return this->m_LoadStatus;
}
C::CKContext* Level::GetCKContext() const {
CHECK_STATUS(this)
return this->m_Context;
}
const std::vector<O::CKGroup*>& Level::GetGroups() const {
CHECK_STATUS(this)
return this->m_ObjGroups;
}
const std::vector<O::CK3dObject*>& Level::Get3dObjects() const {
CHECK_STATUS(this)
return this->m_Obj3dObjects;
}
const std::vector<O::CKMesh*>& Level::GetMeshes() const {
CHECK_STATUS(this)
return this->m_ObjMeshes;
}
const std::vector<O::CKMaterial*>& Level::GetMaterials() const {
CHECK_STATUS(this)
return this->m_ObjMaterials;
}
const std::vector<O::CKTexture*>& Level::GetTextures() const {
CHECK_STATUS(this)
return this->m_ObjTextures;
}
const std::vector<O::CKTargetLight*>& Level::GetTargetLights() const {
CHECK_STATUS(this)
return this->m_ObjTargetLights;
}
#undef CHECK_STATUS
#pragma endregion
Result<Level> load(const Cli::Args& args) {
Level rv(args);
auto status = rv.GetLoadStatus();
if (status.has_value()) return rv;
else return std::unexpected(status.error());
}
} // namespace BMapInspector::Map

View File

@@ -0,0 +1,68 @@
#pragma once
#include "Cli.hpp"
#include <yycc.hpp>
#include <yycc/macro/class_copy_move.hpp>
#include <string>
#include <vector>
#include <optional>
#include <expected>
namespace LibCmo::CK2 {
class CKContext;
namespace ObjImpls {
class CKGroup;
class CK3dObject;
class CKMesh;
class CKMaterial;
class CKTexture;
class CKTargetLight;
} // namespace ObjImpls
} // namespace LibCmo::CK2
namespace BMapInspector::Map {
enum class Error {
BadTempDir, ///< Fail to set temporary directory.
BadBallance, ///< Fail to set Ballance texture directory.
BadEncoding, ///< Fail to set encoding for loading map.
BadMap, ///< Can not load given map.
};
template<typename T>
using Result = std::expected<T, Error>;
class Level {
public:
Level(const Cli::Args& args);
~Level();
YYCC_DELETE_COPY(Level)
YYCC_DECL_MOVE(Level)
public:
Result<void> GetLoadStatus() const;
LibCmo::CK2::CKContext* GetCKContext() const;
private:
LibCmo::CK2::CKContext* m_Context;
Result<void> m_LoadStatus; ///< Nothing or error occurs when loading user given map.
public:
const std::vector<LibCmo::CK2::ObjImpls::CKGroup*>& GetGroups() const;
const std::vector<LibCmo::CK2::ObjImpls::CK3dObject*>& Get3dObjects() const;
const std::vector<LibCmo::CK2::ObjImpls::CKMesh*>& GetMeshes() const;
const std::vector<LibCmo::CK2::ObjImpls::CKMaterial*>& GetMaterials() const;
const std::vector<LibCmo::CK2::ObjImpls::CKTexture*>& GetTextures() const;
const std::vector<LibCmo::CK2::ObjImpls::CKTargetLight*>& GetTargetLights() const;
private:
std::vector<LibCmo::CK2::ObjImpls::CKGroup*> m_ObjGroups;
std::vector<LibCmo::CK2::ObjImpls::CK3dObject*> m_Obj3dObjects;
std::vector<LibCmo::CK2::ObjImpls::CKMesh*> m_ObjMeshes;
std::vector<LibCmo::CK2::ObjImpls::CKMaterial*> m_ObjMaterials;
std::vector<LibCmo::CK2::ObjImpls::CKTexture*> m_ObjTextures;
std::vector<LibCmo::CK2::ObjImpls::CKTargetLight*> m_ObjTargetLights;
};
Result<Level> load(const Cli::Args& args);
} // namespace BMapInspector::Map

View File

@@ -0,0 +1,31 @@
#include "Rule.hpp"
namespace BMapInspector::Rule {
#pragma region IRule
IRule::IRule() {}
IRule::~IRule() {}
#pragma endregion
#pragma region Ruleset
Ruleset::Ruleset() : rules() {
// TODO: create instance for each rules.
}
Ruleset::~Ruleset() {}
size_t Ruleset::GetRuleCount() const {
return this->rules.size();
}
const std::vector<IRule*>& Ruleset::GetRules() const {
return this->rules;
}
#pragma endregion
} // namespace BMapInspector::Ruleset

View File

@@ -1,6 +1,7 @@
#pragma once
#include "Utils.hpp"
#include "Reporter.hpp"
#include "Map.hpp"
#include <VTAll.hpp>
#include <yycc.hpp>
#include <yycc/macro/class_copy_move.hpp>
@@ -8,21 +9,7 @@
#include <string_view>
#include <vector>
namespace BMapInspector::Ruleset {
class RuleContext {
public:
RuleContext();
~RuleContext();
YYCC_DELETE_COPY(RuleContext)
YYCC_DECL_MOVE(RuleContext)
public:
LibCmo::CK2::CKContext *GetCKContext();
private:
LibCmo::CK2::CKContext *m_Ctx;
};
namespace BMapInspector::Rule {
class IRule {
public:
@@ -32,14 +19,14 @@ namespace BMapInspector::Ruleset {
public:
virtual std::u8string_view GetRuleName() const = 0;
virtual void Check(Reporter::Reporter& reporter, RuleContext& ctx) const = 0;
virtual void Check(Reporter::Reporter& reporter, Map::Level& ctx) const = 0;
};
class RuleCollection {
class Ruleset {
public:
RuleCollection();
~RuleCollection();
YYCC_DELETE_COPY_MOVE(RuleCollection)
Ruleset();
~Ruleset();
YYCC_DELETE_COPY_MOVE(Ruleset)
public:
size_t GetRuleCount() const;

View File

@@ -1,53 +0,0 @@
#include "Ruleset.hpp"
namespace BMapInspector::Ruleset {
#pragma region Rule Context
RuleContext::RuleContext() : m_Ctx(nullptr) {}
RuleContext::~RuleContext() {}
LibCmo::CK2::CKContext* RuleContext::GetCKContext() {
return this->m_Ctx;
}
YYCC_IMPL_MOVE_CTOR(RuleContext, rhs) : m_Ctx(rhs.m_Ctx) {
rhs.m_Ctx = nullptr;
}
YYCC_IMPL_MOVE_OPER(RuleContext, rhs) {
this->m_Ctx = rhs.m_Ctx;
rhs.m_Ctx = nullptr;
return *this;
}
#pragma endregion
#pragma region IRule
IRule::IRule() {}
IRule::~IRule() {}
#pragma endregion
#pragma region Rule Collection
RuleCollection::RuleCollection() : rules() {
// TODO: create instance for each rules.
}
RuleCollection::~RuleCollection() {}
size_t RuleCollection::GetRuleCount() const {
return this->rules.size();
}
const std::vector<IRule*>& RuleCollection::GetRules() const {
return this->rules;
}
#pragma endregion
} // namespace BMapInspector::Ruleset