lua: change the way native functions are binded (#1543)
This commit is contained in:
parent
5fc6ef8fb5
commit
c3121de8e7
@ -32,6 +32,13 @@ file(GLOB_RECURSE SRC_MAIN
|
|||||||
"${SRC_DIR}/**.cxx"
|
"${SRC_DIR}/**.cxx"
|
||||||
"${SRC_DIR}/**.asm"
|
"${SRC_DIR}/**.asm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
add_compile_options(/bigobj)
|
||||||
|
else ()
|
||||||
|
add_compile_options(-Wa,-mbig-obj)
|
||||||
|
endif ()
|
||||||
|
|
||||||
add_library(YimMenu MODULE "${SRC_MAIN}")
|
add_library(YimMenu MODULE "${SRC_MAIN}")
|
||||||
|
|
||||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||||
|
@ -3,7 +3,7 @@ include(FetchContent)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
gtav_classes
|
gtav_classes
|
||||||
GIT_REPOSITORY https://github.com/Yimura/GTAV-Classes.git
|
GIT_REPOSITORY https://github.com/Yimura/GTAV-Classes.git
|
||||||
GIT_TAG 7b8bfba701d70e6a503c0767a5bc3b6c4c0294b8
|
GIT_TAG d8304d3e608dac2c22754962420c19f6e74b2c47
|
||||||
GIT_PROGRESS TRUE
|
GIT_PROGRESS TRUE
|
||||||
CONFIGURE_COMMAND ""
|
CONFIGURE_COMMAND ""
|
||||||
BUILD_COMMAND ""
|
BUILD_COMMAND ""
|
||||||
|
@ -1,78 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "invoker.hpp"
|
#include "lua/sol.hpp"
|
||||||
#include "lua/lua_module.hpp"
|
#include "lua/natives/lua_native_binding.hpp"
|
||||||
#include "lua/natives/natives_data.hpp"
|
|
||||||
#include "memory.hpp"
|
|
||||||
|
|
||||||
namespace lua::native
|
namespace lua::native
|
||||||
{
|
{
|
||||||
template<typename T>
|
|
||||||
T invoke(sol::variadic_args args)
|
|
||||||
{
|
|
||||||
big::g_native_invoker.begin_call();
|
|
||||||
|
|
||||||
for (int i = 1; i < args.size(); i++)
|
|
||||||
{
|
|
||||||
if (args[i].is<memory::pointer>())
|
|
||||||
big::g_native_invoker.push_arg(args[i].get<memory::pointer>().get_address());
|
|
||||||
else if (args[i].is<int>())
|
|
||||||
big::g_native_invoker.push_arg(args[i].get<int>());
|
|
||||||
else if (args[i].is<float>())
|
|
||||||
big::g_native_invoker.push_arg(args[i].get<float>());
|
|
||||||
else if (args[i].is<bool>())
|
|
||||||
big::g_native_invoker.push_arg(args[i].get<bool>());
|
|
||||||
else if (args[i].is<const char*>())
|
|
||||||
big::g_native_invoker.push_arg(args[i].get<const char*>());
|
|
||||||
else if (args[i].is<rage::fvector3>())
|
|
||||||
{
|
|
||||||
auto vec = args[i].get<rage::fvector3>();
|
|
||||||
big::g_native_invoker.push_arg(vec.x);
|
|
||||||
big::g_native_invoker.push_arg(vec.y);
|
|
||||||
big::g_native_invoker.push_arg(vec.z);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG(FATAL) << "Unhandled parameter";
|
|
||||||
return T();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
big::g_native_invoker.end_call(args[0].as<std::uint64_t>());
|
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, std::string>)
|
|
||||||
{
|
|
||||||
return std::string(big::g_native_invoker.get_return_value<const char*>());
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, rage::fvector3>)
|
|
||||||
{
|
|
||||||
auto& vec = big::g_native_invoker.get_return_value<Vector3>();
|
|
||||||
return {vec.x, vec.y, vec.z};
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, memory::pointer>)
|
|
||||||
{
|
|
||||||
return memory::pointer(big::g_native_invoker.get_return_value<std::uint64_t>());
|
|
||||||
}
|
|
||||||
else if constexpr (!std::is_void_v<T>)
|
|
||||||
{
|
|
||||||
return big::g_native_invoker.get_return_value<T>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void bind(sol::state& state)
|
void bind(sol::state& state)
|
||||||
{
|
{
|
||||||
auto ns = state["_natives"].get_or_create<sol::table>();
|
init_native_binding(state);
|
||||||
ns["invoke_void"] = invoke<void>;
|
|
||||||
ns["invoke_int"] = invoke<int>;
|
|
||||||
ns["invoke_float"] = invoke<float>;
|
|
||||||
ns["invoke_bool"] = invoke<bool>;
|
|
||||||
ns["invoke_str"] = invoke<std::string>;
|
|
||||||
ns["invoke_vec3"] = invoke<rage::fvector3>;
|
|
||||||
ns["invoke_ptr"] = invoke<memory::pointer>;
|
|
||||||
|
|
||||||
auto result = state.load_buffer(natives_data, natives_size);
|
|
||||||
if (!result.valid())
|
|
||||||
LOG(FATAL) << "Failed to load natives data: " << result.get<sol::error>().what();
|
|
||||||
|
|
||||||
result();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,15 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "lua/sol.hpp"
|
||||||
|
|
||||||
namespace lua::vector
|
namespace lua::vector
|
||||||
{
|
{
|
||||||
static void bind(sol::state& state)
|
static void bind(sol::state& state)
|
||||||
{
|
{
|
||||||
// clang-format off
|
//clang-format off
|
||||||
state.new_usertype<rage::fvector3>("vec3", sol::constructors<rage::fvector3(float, float, float)>(),
|
state.new_usertype<Vector3>("vec3",
|
||||||
"x", &rage::fvector3::x,
|
sol::constructors<Vector3(float, float, float)>(),
|
||||||
"y", &rage::fvector3::y,
|
"x", &Vector3::x, "y", &Vector3::y, "z", &Vector3::z,
|
||||||
"z", &rage::fvector3::z
|
"__tostring", &Vector3::to_string
|
||||||
);
|
);
|
||||||
// clang-format on
|
//clang-format on
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -22,13 +22,13 @@ namespace big
|
|||||||
{
|
{
|
||||||
std::lock_guard guard(m_module_lock);
|
std::lock_guard guard(m_module_lock);
|
||||||
|
|
||||||
for (auto& module : m_modules)
|
for (const auto& module : m_modules)
|
||||||
{
|
{
|
||||||
if (auto it = module->m_gui.find(tab_hash); it != module->m_gui.end())
|
if (const auto it = module->m_gui.find(tab_hash); it != module->m_gui.end())
|
||||||
{
|
{
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
for (auto& element : it->second)
|
for (const auto& element : it->second)
|
||||||
element->draw();
|
element->draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,9 +47,9 @@ namespace big
|
|||||||
{
|
{
|
||||||
std::lock_guard guard(m_module_lock);
|
std::lock_guard guard(m_module_lock);
|
||||||
|
|
||||||
auto id = rage::joaat(module_name);
|
const auto id = rage::joaat(module_name);
|
||||||
|
|
||||||
for (auto& module : m_modules)
|
for (const auto& module : m_modules)
|
||||||
if (module->module_id() == id)
|
if (module->module_id() == id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -58,14 +58,14 @@ namespace big
|
|||||||
|
|
||||||
std::weak_ptr<lua_module> lua_manager::get_module(rage::joaat_t module_id)
|
std::weak_ptr<lua_module> lua_manager::get_module(rage::joaat_t module_id)
|
||||||
{
|
{
|
||||||
for (auto& module : m_modules)
|
for (const auto& module : m_modules)
|
||||||
if (module->module_id() == module_id)
|
if (module->module_id() == module_id)
|
||||||
return module;
|
return module;
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::shared_ptr<lua_module>>& lua_manager::get_modules()
|
const std::vector<std::shared_ptr<lua_module>>& lua_manager::get_modules() const
|
||||||
{
|
{
|
||||||
return m_modules;
|
return m_modules;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ namespace big
|
|||||||
void unload_module(rage::joaat_t module_id);
|
void unload_module(rage::joaat_t module_id);
|
||||||
void load_module(const std::string& module_name);
|
void load_module(const std::string& module_name);
|
||||||
std::weak_ptr<lua_module> get_module(rage::joaat_t module_id);
|
std::weak_ptr<lua_module> get_module(rage::joaat_t module_id);
|
||||||
const std::vector<std::shared_ptr<lua_module>>& get_modules();
|
const std::vector<std::shared_ptr<lua_module>>& get_modules() const;
|
||||||
void reload_all_modules();
|
void reload_all_modules();
|
||||||
void handle_error(const sol::error& error, const sol::state_view& state);
|
void handle_error(const sol::error& error, const sol::state_view& state);
|
||||||
|
|
||||||
|
@ -50,27 +50,19 @@ namespace big
|
|||||||
{
|
{
|
||||||
m_state.open_libraries();
|
m_state.open_libraries();
|
||||||
|
|
||||||
lua::log::bind(m_state);
|
const auto scripts_folder = g_file_manager->get_project_folder("scripts");
|
||||||
lua::globals::bind(m_state);
|
|
||||||
lua::script::bind(m_state);
|
add_folder_to_require_available_paths(scripts_folder);
|
||||||
lua::native::bind(m_state);
|
|
||||||
lua::memory::bind(m_state);
|
init_lua_api();
|
||||||
lua::gui::bind(m_state);
|
|
||||||
lua::network::bind(m_state);
|
|
||||||
lua::command::bind(m_state);
|
|
||||||
lua::tunables::bind(m_state);
|
|
||||||
lua::locals::bind(m_state);
|
|
||||||
lua::event::bind(m_state);
|
|
||||||
lua::vector::bind(m_state);
|
|
||||||
|
|
||||||
m_state["!module_name"] = module_name;
|
m_state["!module_name"] = module_name;
|
||||||
m_state["!this"] = this;
|
m_state["!this"] = this;
|
||||||
m_state["joaat"] = rage::joaat;
|
|
||||||
|
|
||||||
m_state.set_exception_handler((sol::exception_handler_function)exception_handler);
|
m_state.set_exception_handler((sol::exception_handler_function)exception_handler);
|
||||||
m_state.set_panic(panic_handler);
|
m_state.set_panic(panic_handler);
|
||||||
|
|
||||||
auto result = m_state.load_file(g_file_manager->get_project_folder("scripts").get_file(module_name).get_path().string());
|
auto result = m_state.load_file(scripts_folder.get_file(module_name).get_path().string());
|
||||||
|
|
||||||
if (!result.valid())
|
if (!result.valid())
|
||||||
{
|
{
|
||||||
@ -106,4 +98,28 @@ namespace big
|
|||||||
{
|
{
|
||||||
return m_module_name;
|
return m_module_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void lua_module::add_folder_to_require_available_paths(const big::folder& scripts_folder)
|
||||||
|
{
|
||||||
|
const std::string package_path = m_state["package"]["path"];
|
||||||
|
const auto scripts_search_path = scripts_folder.get_path() / "?.lua";
|
||||||
|
m_state["package"]["path"] = package_path + (!package_path.empty() ? ";" : "") + scripts_search_path.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
void lua_module::init_lua_api()
|
||||||
|
{
|
||||||
|
lua::log::bind(m_state);
|
||||||
|
lua::globals::bind(m_state);
|
||||||
|
lua::script::bind(m_state);
|
||||||
|
lua::native::bind(m_state);
|
||||||
|
lua::memory::bind(m_state);
|
||||||
|
lua::gui::bind(m_state);
|
||||||
|
lua::network::bind(m_state);
|
||||||
|
lua::command::bind(m_state);
|
||||||
|
lua::tunables::bind(m_state);
|
||||||
|
lua::locals::bind(m_state);
|
||||||
|
lua::event::bind(m_state);
|
||||||
|
lua::vector::bind(m_state);
|
||||||
|
m_state["joaat"] = rage::joaat;
|
||||||
|
}
|
||||||
}
|
}
|
@ -22,7 +22,13 @@ namespace big
|
|||||||
|
|
||||||
lua_module(std::string module_name);
|
lua_module(std::string module_name);
|
||||||
~lua_module();
|
~lua_module();
|
||||||
|
|
||||||
rage::joaat_t module_id();
|
rage::joaat_t module_id();
|
||||||
const std::string& module_name();
|
const std::string& module_name();
|
||||||
|
|
||||||
|
// used for adding our own paths to the search paths of the lua require function
|
||||||
|
void add_folder_to_require_available_paths(const big::folder& scripts_folder);
|
||||||
|
|
||||||
|
void init_lua_api();
|
||||||
};
|
};
|
||||||
}
|
}
|
6593
src/lua/natives/lua_native_binding.cpp
Normal file
6593
src/lua/natives/lua_native_binding.cpp
Normal file
File diff suppressed because it is too large
Load Diff
7
src/lua/natives/lua_native_binding.hpp
Normal file
7
src/lua/natives/lua_native_binding.hpp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "lua/sol.hpp"
|
||||||
|
|
||||||
|
namespace lua::native
|
||||||
|
{
|
||||||
|
void init_native_binding(sol::state& L);
|
||||||
|
}
|
118122
src/lua/natives/natives.json
118122
src/lua/natives/natives.json
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
extern char natives_data[];
|
|
||||||
extern int natives_size;
|
|
@ -1,87 +1,164 @@
|
|||||||
import json
|
import os
|
||||||
|
|
||||||
natives = json.load(open("natives.json"))
|
natives_hpp = open("../../natives.hpp", "r")
|
||||||
out_file : str = ""
|
|
||||||
|
|
||||||
def sanitize_param(name):
|
cpp_print_buf = ""
|
||||||
if name in ["repeat", "end"]:
|
hpp_print_buf = ""
|
||||||
return "_"+name
|
|
||||||
else:
|
|
||||||
return name
|
|
||||||
|
|
||||||
def make_param_listing(params):
|
|
||||||
pms = ""
|
|
||||||
for param in params:
|
|
||||||
pms += sanitize_param(param["name"])
|
|
||||||
pms += ","
|
|
||||||
pms = pms.rstrip(",")
|
|
||||||
return pms
|
|
||||||
|
|
||||||
def is_string(type: str):
|
|
||||||
return type.find("char*") != -1
|
|
||||||
|
|
||||||
def is_pointer(type: str):
|
|
||||||
"""also returns true for string"""
|
|
||||||
return type.find('*') != -1
|
|
||||||
|
|
||||||
|
|
||||||
def write_native(name, hash, params, return_type):
|
def print_cpp(text):
|
||||||
global out_file
|
global cpp_print_buf
|
||||||
|
cpp_print_buf += text + "\n"
|
||||||
|
|
||||||
out_file += f"{name}=function({make_param_listing(params)})"
|
|
||||||
|
|
||||||
invoke_type = "invoke_int"
|
def print_hpp(text):
|
||||||
|
global hpp_print_buf
|
||||||
|
hpp_print_buf += text + "\n"
|
||||||
|
|
||||||
if (return_type == "void"):
|
|
||||||
invoke_type = "invoke_void"
|
|
||||||
elif (return_type == "float"):
|
|
||||||
invoke_type = "invoke_float"
|
|
||||||
elif (return_type == "BOOL"):
|
|
||||||
invoke_type = "invoke_bool"
|
|
||||||
elif (return_type == "const char*"):
|
|
||||||
invoke_type = "invoke_str"
|
|
||||||
elif (return_type == "Vector3"):
|
|
||||||
invoke_type = "invoke_vec3"
|
|
||||||
elif (return_type.endswith("*")):
|
|
||||||
invoke_type = "invoke_ptr"
|
|
||||||
|
|
||||||
out_file += f"return _natives.{invoke_type}({hash},"
|
class Arg:
|
||||||
for param in params:
|
def __init__(self, name, type_):
|
||||||
out_file += f"{sanitize_param(param['name'])},"
|
self.name = name
|
||||||
out_file = out_file.removesuffix(",")
|
self.type_ = type_
|
||||||
out_file += ");end,\n"
|
|
||||||
|
|
||||||
def write_namespace(name, data):
|
def __str__(self) -> str:
|
||||||
global out_file
|
return str(self.type_) + " " + str(self.name)
|
||||||
|
|
||||||
out_file += f"{name} = {{\n"
|
|
||||||
|
|
||||||
for (hash, more) in data.items():
|
class NativeFunc:
|
||||||
write_native(more["name"], hash, more["params"], more["return_type"])
|
def __init__(self, name, args, return_type):
|
||||||
|
self.name = name
|
||||||
|
self.args = args
|
||||||
|
self.return_type = return_type
|
||||||
|
|
||||||
out_file += "};\n"
|
def __str__(self) -> str:
|
||||||
|
if len(self.args) > 0:
|
||||||
|
return (
|
||||||
|
str(self.return_type)
|
||||||
|
+ " "
|
||||||
|
+ str(self.name)
|
||||||
|
+ "( "
|
||||||
|
+ str(self.args[0])
|
||||||
|
+ " )"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return str(self.return_type) + " " + str(self.name) + "( )"
|
||||||
|
|
||||||
def write_file():
|
|
||||||
for (namespace, data) in natives.items():
|
|
||||||
write_namespace(namespace, data)
|
|
||||||
|
|
||||||
def convert_and_write_cpp_file():
|
def get_natives_func_from_natives_hpp_file(natives_hpp):
|
||||||
global out_file
|
functions_per_namespaces = {}
|
||||||
|
current_namespace = ""
|
||||||
|
func_name = ""
|
||||||
|
start_parsing = False
|
||||||
|
for line in natives_hpp.readlines():
|
||||||
|
if "namespace SYSTEM" not in line and not start_parsing:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
start_parsing = True
|
||||||
|
|
||||||
cpp_data = "#pragma once\n// clang-format off\n// Generated by natives_gen.py. DO NOT EDIT\nchar natives_data[] = \n"
|
if not start_parsing:
|
||||||
|
continue
|
||||||
|
|
||||||
lines = out_file.rstrip('\n').splitlines()
|
if "namespace " in line:
|
||||||
for line in lines:
|
current_namespace = line.replace("namespace ", "").strip()
|
||||||
cpp_data += f"\"{line}\\n\"\\\n"
|
functions_per_namespaces[current_namespace] = []
|
||||||
|
elif "NATIVE_DECL" in line:
|
||||||
|
words = line.split()
|
||||||
|
|
||||||
cpp_data = cpp_data.rstrip('\n\\')
|
# remove NATIVE_DECL from the words array
|
||||||
cpp_data += ";\n// clang-format on\n"
|
words.pop(0)
|
||||||
cpp_data += "int natives_size = sizeof(natives_data)-1;"
|
|
||||||
open("natives_data.cpp", "w+").write(cpp_data)
|
|
||||||
|
|
||||||
def write_lua_file():
|
func_name = ""
|
||||||
open("natives.lua", "w+").write(out_file)
|
return_type = [x for x in words if "(" not in x][0]
|
||||||
|
|
||||||
if __name__ == "__main__":
|
for word in words:
|
||||||
write_file()
|
# function name + args
|
||||||
convert_and_write_cpp_file()
|
if "(" in word:
|
||||||
|
if func_name == "":
|
||||||
|
func_name_and_args = word.split("(")
|
||||||
|
func_name = func_name_and_args[0]
|
||||||
|
continue
|
||||||
|
|
||||||
|
functions_per_namespaces[current_namespace].append(
|
||||||
|
NativeFunc(func_name, [], return_type)
|
||||||
|
)
|
||||||
|
|
||||||
|
return functions_per_namespaces
|
||||||
|
|
||||||
|
|
||||||
|
functions_per_namespaces = get_natives_func_from_natives_hpp_file(natives_hpp)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_native_binding_cpp_and_hpp_file(functions_per_namespaces):
|
||||||
|
generated_function_name = "void init_native_binding(sol::state& L)"
|
||||||
|
|
||||||
|
print_hpp("#pragma once")
|
||||||
|
print_hpp('#include "lua/sol.hpp"')
|
||||||
|
print_hpp("")
|
||||||
|
print_hpp("namespace lua::native")
|
||||||
|
print_hpp("{")
|
||||||
|
print_hpp("\t" + generated_function_name + ";")
|
||||||
|
print_hpp("}")
|
||||||
|
|
||||||
|
print_cpp('#include "lua_native_binding.hpp"')
|
||||||
|
print_cpp('#include "natives.hpp"')
|
||||||
|
print_cpp("")
|
||||||
|
print_cpp("namespace lua::native")
|
||||||
|
print_cpp("{")
|
||||||
|
print_cpp("\t" + generated_function_name)
|
||||||
|
print_cpp("\t{")
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for namespace_name, native_funcs in functions_per_namespaces.items():
|
||||||
|
print_cpp(
|
||||||
|
"\t\tauto "
|
||||||
|
+ namespace_name
|
||||||
|
+ ' = L["'
|
||||||
|
+ namespace_name
|
||||||
|
+ '"].get_or_create<sol::table>();'
|
||||||
|
)
|
||||||
|
|
||||||
|
for native_func in native_funcs:
|
||||||
|
i += 1
|
||||||
|
print_cpp(
|
||||||
|
"\t\t"
|
||||||
|
+ namespace_name
|
||||||
|
+ '.set_function("'
|
||||||
|
+ native_func.name
|
||||||
|
+ '", '
|
||||||
|
+ namespace_name
|
||||||
|
+ "::"
|
||||||
|
+ native_func.name
|
||||||
|
+ ");"
|
||||||
|
)
|
||||||
|
|
||||||
|
print_cpp("")
|
||||||
|
|
||||||
|
print(f"Wrote binding for {i} native functions")
|
||||||
|
print_cpp("\t}")
|
||||||
|
print_cpp("}")
|
||||||
|
|
||||||
|
|
||||||
|
generate_native_binding_cpp_and_hpp_file(functions_per_namespaces)
|
||||||
|
|
||||||
|
|
||||||
|
def write_cpp_code(cpp_print_buf):
|
||||||
|
file_name = "lua_native_binding.cpp"
|
||||||
|
if os.path.exists(file_name):
|
||||||
|
os.remove(file_name)
|
||||||
|
f = open(file_name, "a")
|
||||||
|
f.write(cpp_print_buf)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
def write_hpp_code(hpp_print_buf):
|
||||||
|
file_name = "lua_native_binding.hpp"
|
||||||
|
if os.path.exists(file_name):
|
||||||
|
os.remove(file_name)
|
||||||
|
f = open(file_name, "a")
|
||||||
|
f.write(hpp_print_buf)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
write_cpp_code(cpp_print_buf)
|
||||||
|
write_hpp_code(hpp_print_buf)
|
||||||
|
Reference in New Issue
Block a user