#include "property.h" #include "m2_tokvec.h" // read_file #include #include #include #include #include namespace fmt { namespace fs = std::filesystem; std::string Property::get(const std::string& key, const std::string& def) const { auto it = kv.find(key); return it == kv.end() ? def : it->second; } PropertyType property_type_from_string(const std::string& s) { std::string l = s; std::transform(l.begin(), l.end(), l.begin(), [](unsigned char c) { return (char)std::tolower(c); }); if (l == "tree") return PropertyType::Tree; if (l == "building") return PropertyType::Building; if (l == "effect") return PropertyType::Effect; if (l == "ambience") return PropertyType::Ambience; if (l == "dungeonblock") return PropertyType::DungeonBlock; return PropertyType::Unknown; } bool parse_property(const std::string& text, Property& out, std::string* err) { out = Property{}; std::istringstream in(text); std::string line; // 行1: YPRT if (!std::getline(in, line)) { if (err) *err = "空文件"; return false; } while (!line.empty() && (line.back() == '\r' || line.back() == ' ' || line.back() == '\t')) line.pop_back(); { std::string t; for (char c : line) if (!std::isspace((unsigned char)c)) t += c; if (t != "YPRT") { if (err) *err = "首行不是 YPRT: '" + t + "'"; return false; } } // 行2: crc if (!std::getline(in, line)) { if (err) *err = "缺 CRC 行"; return false; } out.crc = (uint32_t)std::strtoul(line.c_str(), nullptr, 10); if (out.crc == 0) { if (err) *err = "CRC 行解析为 0: '" + line + "'"; return false; } // 行3+: key\t"value" while (std::getline(in, line)) { if (!line.empty() && line.back() == '\r') line.pop_back(); // key = 第一段非空白 size_t i = 0, n = line.size(); while (i < n && std::isspace((unsigned char)line[i])) ++i; if (i >= n) continue; size_t ks = i; while (i < n && !std::isspace((unsigned char)line[i])) ++i; std::string key = line.substr(ks, i - ks); std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) { return (char)std::tolower(c); }); // value = 引号内,或剩余去空白 while (i < n && std::isspace((unsigned char)line[i])) ++i; std::string val; if (i < n && line[i] == '"') { ++i; while (i < n && line[i] != '"') val += line[i++]; } else { val = line.substr(i); while (!val.empty() && (val.back() == '\r' || std::isspace((unsigned char)val.back()))) val.pop_back(); } out.kv[key] = val; } out.name = out.get("propertyname"); out.type_str = out.get("propertytype"); out.type = property_type_from_string(out.type_str); return true; } bool parse_property_file(const std::string& path, Property& out, std::string* err) { std::string text; if (!read_file(path, text)) { if (err) *err = "打不开 " + path; return false; } if (!parse_property(text, out, err)) return false; out.source_path = path; return true; } const Property* PropertyRegistry::find(uint32_t crc) const { auto it = by_crc.find(crc); return it == by_crc.end() ? nullptr : &it->second; } bool PropertyRegistry::scan(const std::string& root, std::string* err) { std::error_code ec; if (!fs::exists(root, ec)) { if (err) *err = "目录不存在: " + root; return false; } static const char* kExts[] = {".prb", ".prt", ".pre", ".prd", ".pra"}; for (auto it = fs::recursive_directory_iterator( root, fs::directory_options::skip_permission_denied, ec); it != fs::recursive_directory_iterator(); it.increment(ec)) { if (ec) { ec.clear(); continue; } if (!it->is_regular_file(ec)) continue; std::string ext = it->path().extension().string(); std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return (char)std::tolower(c); }); if (std::find(std::begin(kExts), std::end(kExts), ext) == std::end(kExts)) continue; ++files_scanned; Property p; std::string e2; if (!parse_property_file(it->path().string(), p, &e2)) { ++parse_failed; failed_paths.push_back(it->path().string() + " : " + e2); continue; } type_counts[p.type_str.empty() ? "" : p.type_str]++; auto [ins, ok] = by_crc.emplace(p.crc, p); if (!ok) ++crc_collisions; } return true; } bool PropertyRegistry::scan_list(const std::string& root, const std::vector& rel_paths, std::string* err) { static const char* kExts[] = {".prb", ".prt", ".pre", ".prd", ".pra"}; std::string r = root; if (!r.empty() && r.back() == '/') r.pop_back(); for (const std::string& rel : rel_paths) { // 只认顶层 Property/ 下的(对齐 scan(root + "/Property"),不吃 patch 覆盖) if (rel.size() < 9) continue; { std::string top = rel.substr(0, 8); std::transform(top.begin(), top.end(), top.begin(), [](unsigned char c) { return (char)std::tolower(c); }); if (top != "property" || (rel[8] != '/' && rel[8] != '\\')) continue; } std::string ext; size_t dot = rel.find_last_of('.'); if (dot != std::string::npos) { ext = rel.substr(dot); std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return (char)std::tolower(c); }); } if (std::find(std::begin(kExts), std::end(kExts), ext) == std::end(kExts)) continue; ++files_scanned; Property p; std::string e2; std::string full = r + "/" + rel; if (!parse_property_file(full, p, &e2)) { // read_file -> host FileAccess -> res:// ok ++parse_failed; failed_paths.push_back(full + " : " + e2); continue; } type_counts[p.type_str.empty() ? "" : p.type_str]++; auto [ins, ok] = by_crc.emplace(p.crc, p); if (!ok) ++crc_collisions; } if (files_scanned == 0) { if (err) *err = "清单里没有属性文件(.pr*)"; return false; } return true; } } // namespace fmt