Add ForEachString function + lcfstrings example tool
MODIFY
CMakeLists.txt
CMakeLists.txt
@@ -194,6 +194,7 @@ set(LCF_SOURCES
)
set(LCF_HEADERS
+ src/lcf/context.h
src/lcf/data.h
src/lcf/dbarray.h
src/lcf/dbarrayalloc.h
@@ -452,6 +453,7 @@ endforeach()
if(NOT DISABLE_TOOLS)
add_custom_target(tools)
add_dependencies(tools lcf)
+ list(APPEND TOOLS lcfstrings)
if(NOT DISABLE_XML)
list(APPEND TOOLS lcf2xml)
endif()

MODIFY
Makefile.am
Makefile.am
@@ -201,6 +201,7 @@ liblcf_la_SOURCES = \
src/generated/rpg_variable.cpp
lcfinclude_HEADERS = \
+ src/lcf/context.h \
src/lcf/data.h \
src/lcf/dbarray.h \
src/lcf/dbarrayalloc.h \

MODIFY
generator/generate.py
generator/generate.py
@@ -265,7 +265,12 @@ def get_structs(*filenames):
elem = Struct(elem.name, elem.base, bool(int(elem.hasid)) if elem.hasid else None)
processed_result[k].append(elem)
- return processed_result
+ processed_flat = []
+ for filetype, struct in processed_result.items():
+ for elem in struct:
+ processed_flat.append(elem)
+
+ return processed_result, processed_flat
def get_fields(*filenames):
Field = namedtuple("Field", "name size type code default presentifdefault is2k3 comment")
@@ -317,10 +322,8 @@ def get_constants(filename='constants.csv'):
def get_headers():
header_map = dict()
- structs_flat = []
for filetype, struct in structs.items():
for elem in struct:
- structs_flat.append(elem)
header_map[elem.name] = elem.name.lower()
result = {}
@@ -350,9 +353,19 @@ def needs_ctor(struct_name):
return struct_name in setup and any('Init()' in method
for method, hdrs in setup[struct_name])
+def type_is_db_string(ty):
+ return ty == 'DBString'
+
def type_is_array(ty):
return re.match(r'(Vector|Array|DBArray)<(.*)>', ty) or ty == "DBBitArray"
+def type_is_struct(ty):
+ return ty in [ x.name for x in structs_flat ]
+
+def type_is_array_of_struct(ty):
+ m = re.match(r'(Vector|Array|DBArray)<(.*)>', ty)
+ return m and type_is_struct(m.group(2))
+
def is_monotonic_from_0(enum):
expected = 0
for (val, idx) in enum:
@@ -378,11 +391,6 @@ def generate():
type=filetype
))
- structs_flat = []
- for filetype, struct in structs.items():
- for elem in struct:
- structs_flat.append(elem)
-
filepath = os.path.join(tmp_dir, 'fwd_struct_impl.h')
with openToRender(filepath) as f:
f.write(fwd_struct_tmpl.render(
@@ -459,10 +467,10 @@ def main(argv):
if not os.path.exists(dest_dir):
os.mkdir(dest_dir)
- global structs, sfields, enums, flags, setup, constants, headers
+ global structs, structs_flat, sfields, enums, flags, setup, constants, headers
global chunk_tmpl, lcf_struct_tmpl, rpg_header_tmpl, rpg_source_tmpl, flags_tmpl, enums_tmpl, fwd_tmpl, fwd_struct_tmpl
- structs = get_structs('structs.csv','structs_easyrpg.csv')
+ structs, structs_flat = get_structs('structs.csv','structs_easyrpg.csv')
sfields = get_fields('fields.csv','fields_easyrpg.csv')
enums = get_enums('enums.csv','enums_easyrpg.csv')
flags = get_flags('flags.csv')
@@ -483,10 +491,14 @@ def main(argv):
env.filters["flag_set"] = flag_set
env.tests['needs_ctor'] = needs_ctor
env.tests['monotonic_from_0'] = is_monotonic_from_0
+ env.tests['is_db_string'] = type_is_db_string
env.tests['is_array'] = type_is_array
+ env.tests['is_array_of_struct'] = type_is_array_of_struct
+ env.tests['is_struct'] = type_is_struct
globals = dict(
structs=structs,
+ structs_flat=structs_flat,
fields=sfields,
flags=flags,
enums=enums,

MODIFY
generator/templates/rpg_header.tmpl
generator/templates/rpg_header.tmpl
{%- endif %}
#include {{ header }}
{%- endfor %}
+#include "lcf/context.h"
#include <ostream>
#include <type_traits>
@@ -128,6 +129,28 @@ namespace rpg {
}
std::ostream& operator<<(std::ostream& os, const {{ struct_name }}& obj);
+
+ template <typename F, typename ParentCtx = Context<void,void>>
+ void ForEachString({{ struct_name }}& obj, const F& f, const ParentCtx* parent_ctx = nullptr) {
+ {%- for field in fields[struct_name]|field_is_used|field_is_not_size -%}
+ {%- if field.type is is_db_string %}
+ const auto ctx{{ loop.index }} = Context<{{ struct_name }}, ParentCtx>{ "{{ field.name }}", -1, &obj, parent_ctx };
+ f(obj.{{ field.name }}, ctx{{ loop.index }});
+ {%- elif field.type is is_struct %}
+ const auto ctx{{ loop.index }} = Context<{{ struct_name }}, ParentCtx>{ "{{ field.name }}", -1, &obj, parent_ctx };
+ ForEachString(obj.{{field.name}}, f, &ctx{{ loop.index }});
+ {%- elif field.type is is_array_of_struct %}
+ for (int i = 0; i < static_cast<int>(obj.{{ field.name }}.size()); ++i) {
+ const auto ctx{{ loop.index }} = Context<{{ struct_name }}, ParentCtx>{ "{{ field.name }}", i, &obj, parent_ctx };
+ ForEachString(obj.{{ field.name }}[i], f, &ctx{{ loop.index }});
+ }
+ {%- endif -%}
+ {%- endfor %}
+ (void)obj;
+ (void)f;
+ (void)parent_ctx;
+ }
+
} // namespace rpg
} // namespace lcf

ADD
src/lcf/context.h
+/*
+ * This file is part of liblcf. Copyright (c) 2020 liblcf authors.
+ * https://github.com/EasyRPG/liblcf - https://easyrpg.org
+ *
+ * liblcf is Free/Libre Open Source Software, released under the MIT License.
+ * For the full copyright and license information, please view the COPYING
+ * file that was distributed with this source code.
+ */
+
+#ifndef LCF_CONTEXT_H
+#define LCF_CONTEXT_H
+
+#include "string_view.h"
+
+namespace lcf {
+
+/**
+ * Context information for iteraing lcf fields
+ */
+struct ContextNameBase {
+ /** Constructor */
+ constexpr ContextNameBase(StringView n, int i) : name(n), index(i) {}
+
+ /** Name of the enumerated field */
+ StringView name;
+
+ /** Array index when the object is part of a list (-1 when not) */
+ int index = -1;
+};
+
+/**
+ * Context information for iteraing lcf fields
+ * @tparam StructType The type of the Struct
+ */
+template <typename StructType>
+struct ContextStructBase : ContextNameBase {
+ /** The type of the struct */
+ using StructType_t = StructType;
+
+ /** Constructor */
+ constexpr ContextStructBase(StringView n, int i, StructType_t* o)
+ : ContextNameBase(n, i), obj(o) {}
+
+ /** Object instance (cast to appropriate RPG-type */
+ StructType_t* obj = nullptr;
+};
+
+/**
+ * Context information for iteraing lcf fields
+ * @tparam StructType The type of the Struct
+ * @tparam ParentCtx The type of the parent context.
+ */
+template <typename StructType, typename ParentCtxType>
+struct Context : ContextStructBase<StructType> {
+ /** The type of the struct */
+ using StructType_t = typename ContextStructBase<StructType>::StructType_t;
+
+ /** The type of the parent context */
+ using ParentCtxType_t = ParentCtxType;
+
+ constexpr Context(StringView n, int i, StructType* o, const ParentCtxType_t* pctx)
+ : ContextStructBase<StructType>{n, i, o}, parent(pctx) {}
+
+ /** Context of the parent (nullptr when no parent) */
+ const ParentCtxType_t* parent = nullptr;
+};
+
+} //namespace lcf
+
+#endif

ADD
tools/lcfstrings.cpp
+/*
+ * Copyright (c) 2020 liblcf authors
+ * This file is released under the MIT License
+ * http://opensource.org/licenses/MIT
+ */
+
+#include <cstring>
+#include <iostream>
+#include <string>
+
+#include <lcf/context.h>
+#include <lcf/data.h>
+#include <lcf/ldb/reader.h>
+#include <lcf/lmt/reader.h>
+#include <lcf/lmu/reader.h>
+#include <lcf/lsd/reader.h>
+
+
+static bool HasExt(const std::string& path, const std::string& ext) {
+ if (path.size() < ext.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < ext.size(); ++i) {
+ if (std::tolower(path[path.size()-i-1]) != ext[ext.size()-i-1]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+int index_off = 0;
+std::string delim = "::";
+
+static void print_ctx_info(const lcf::ContextNameBase* ctx) {
+ std::cout << ctx->name;
+
+ if (ctx->index != -1) {
+ if (delim == ".") {
+ std::cout << delim << (ctx->index + index_off);
+ } else {
+ std::cout << "[" << (ctx->index + index_off) << "]";
+ }
+ }
+}
+
+// Needed to stop top level parent recursion
+static void print_parent(const void*) {
+}
+
+template <typename ContextType>
+static void print_parent(const ContextType* ctx) {
+ if (!ctx) {
+ return;
+ }
+
+ print_parent(ctx->parent);
+
+ if (ctx->parent) {
+ std::cout << delim;
+ }
+ print_ctx_info(ctx);
+}
+
+template <typename ContextType>
+static void print(const lcf::DBString& field, const ContextType& ctx) {
+ print_parent(ctx.parent);
+
+ std::cout << delim << ctx.name << "=" << field << "\n";
+}
+
+template <typename T>
+void Dump(T& t) {
+ ForEachString(t, [](const auto& field, auto& ctx) {
+ if (!field.empty()) {
+ print(field, ctx);
+ }
+ });
+}
+
+static int print_help(char** argv) {
+ std::cerr << "lcfstrings - Extract all strings contained in RPG Maker 2000/2003 Files" << std::endl;
+ std::cerr << "Usage: " << argv[0] << " [OPTION...] FILE [ENCODING]" << std::endl;
+ std::cerr << "Options:" << std::endl;
+ std::cerr << " -1, --one Print using a one-based (Delphi) array index" << std::endl;
+ std::cerr << " -h, --help This usage message" << std::endl;
+ std::cerr << " -s, --simple Print field path in a django-like way" << std::endl;
+ return 2;
+}
+
+int main(int argc, char** argv) {
+ std::string path;
+ std::string encoding;
+
+ if (argc <= 1) {
+ return print_help(argv);
+ }
+
+ /* parse command line arguments */
+ for (int i = 1; i < argc; ++i) {
+ std::string arg = argv[i];
+
+ if ((arg == "--help") || (arg == "-h")) {
+ return print_help(argv);
+ } else if ((arg == "--one") || (arg == "-1")) {
+ index_off = 1;
+ } else if ((arg == "--simple") || (arg == "-s")) {
+ delim = ".";
+ } else {
+ path = arg;
+ if (i+1 < argc) {
+ encoding = argv[i+1];
+ }
+ break;
+ }
+ }
+
+ if (path.empty()) {
+ return print_help(argv);
+ }
+
+ bool rc = false;
+
+ if (HasExt(path, ".ldb")) {
+ rc = lcf::LDB_Reader::Load(path, encoding);
+ if (rc) Dump(lcf::Data::data);
+ } else if (HasExt(path, ".lmt")) {
+ rc = !lcf::LMT_Reader::Load(path, encoding);
+ if (rc) Dump(lcf::Data::treemap);
+ } else if (HasExt(path, ".lmu")) {
+ auto file = lcf::LMU_Reader::Load(path, encoding);
+ rc = bool(file);
+ if (rc) Dump(*file);
+ } else if (HasExt(path, ".lsd")) {
+ auto file = lcf::LSD_Reader::Load(path, encoding);
+ rc = bool(file);
+ if (rc) Dump(*file);
+ } else {
+ std::cerr << "Unknown file type!" << std::endl;
+ }
+
+ return !rc;
+}
+

Add ForEachString function + lcfstrings example tool