Skip to content

Commit

Permalink
refactor: ♻️ created *setup_log.gd* and setup_utils.gd* (#211)
Browse files Browse the repository at this point in the history
The tradeoffs for using the existing ModLoader Classes in the setup script are to big. So the required functions where moved into the *setup/* dir.
  • Loading branch information
KANAjetzt authored Apr 17, 2023
1 parent f64d8df commit 5cff450
Show file tree
Hide file tree
Showing 3 changed files with 432 additions and 22 deletions.
51 changes: 29 additions & 22 deletions addons/mod_loader/mod_loader_setup.gd
Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,30 @@ const new_global_classes := [
"class": "ModLoaderSteam",
"language": "GDScript",
"path": "res://addons/mod_loader/api/third_party/steam.gd"
}, {
"base": "Node",
"class": "ModLoaderLog",
"language": "GDScript",
"path": "res://addons/mod_loader/api/log.gd"
}
]

# IMPORTANT: use the ModLoaderUtils via this variable within this script!
# IMPORTANT: use the ModLoaderLog via this variable within this script!
# Otherwise, script compilation will break on first load since the class is not defined.
var modloaderutils: Node = load("res://addons/mod_loader/mod_loader_utils.gd").new()
var ModLoaderSetupLog: Object = load("res://addons/mod_loader/setup/setup_log.gd")
var ModLoaderSetupUtils: Object = load("res://addons/mod_loader/setup/setup_utils.gd")

var path := {}
var file_name := {}
var is_only_setup: bool = modloaderutils.is_running_with_command_line_arg("--only-setup")
var is_setup_create_override_cfg : bool = modloaderutils.is_running_with_command_line_arg("--setup-create-override-cfg")
var is_only_setup: bool = ModLoaderSetupUtils.is_running_with_command_line_arg("--only-setup")
var is_setup_create_override_cfg : bool = ModLoaderSetupUtils.is_running_with_command_line_arg("--setup-create-override-cfg")


func _init() -> void:
modloaderutils.log_debug("ModLoader setup initialized", LOG_NAME)
ModLoaderSetupLog.debug("ModLoader setup initialized", LOG_NAME)

var mod_loader_index: int = modloaderutils.get_autoload_index("ModLoader")
var mod_loader_store_index: int = modloaderutils.get_autoload_index("ModLoaderStore")
var mod_loader_index: int = ModLoaderSetupUtils.get_autoload_index("ModLoader")
var mod_loader_store_index: int = ModLoaderSetupUtils.get_autoload_index("ModLoaderStore")

# Avoid doubling the setup work
# Checks if the ModLoaderStore is the first autoload and ModLoader the second
Expand All @@ -101,7 +107,7 @@ func _init() -> void:

# ModLoader already setup - switch to the main scene
func modded_start() -> void:
modloaderutils.log_info("ModLoader is available, mods can be loaded!", LOG_NAME)
ModLoaderSetupLog.info("ModLoader is available, mods can be loaded!", LOG_NAME)

OS.set_window_title("%s (Modded)" % ProjectSettings.get_setting("application/config/name"))

Expand All @@ -110,13 +116,13 @@ func modded_start() -> void:

# Set up the ModLoader as an autoload and register the other global classes.
func setup_modloader() -> void:
modloaderutils.log_info("Setting up ModLoader", LOG_NAME)
ModLoaderSetupLog.info("Setting up ModLoader", LOG_NAME)

# Setup path and file_name dict with all required paths and file names.
setup_file_data()

# Register all new helper classes as global
modloaderutils.register_global_classes_from_array(new_global_classes)
ModLoaderSetupUtils.register_global_classes_from_array(new_global_classes)

# Add ModLoader autoload (the * marks the path as autoload)
reorder_autoloads()
Expand All @@ -132,7 +138,7 @@ func setup_modloader() -> void:
handle_project_binary()

# ModLoader is set up. A game restart is required to apply the ProjectSettings.
modloaderutils.log_info("ModLoader is set up, a game restart is required.", LOG_NAME)
ModLoaderSetupLog.info("ModLoader is set up, a game restart is required.", LOG_NAME)

match true:
# If the --only-setup cli argument is passed, quit with exit code 0
Expand Down Expand Up @@ -170,13 +176,13 @@ func reorder_autoloads() -> void:

# Saves the ProjectSettings to a override.cfg file in the base game directory.
func handle_override_cfg() -> void:
modloaderutils.log_debug("using the override.cfg file", LOG_NAME)
var _save_custom_error: int = ProjectSettings.save_custom(modloaderutils.get_override_path())
ModLoaderSetupLog.debug("using the override.cfg file", LOG_NAME)
var _save_custom_error: int = ProjectSettings.save_custom(ModLoaderSetupUtils.get_override_path())


# Creates the project.binary file, adds it to the pck and removes the no longer needed project.binary file.
func handle_project_binary() -> void:
modloaderutils.log_debug("injecting the project.binary file", LOG_NAME)
ModLoaderSetupLog.debug("injecting the project.binary file", LOG_NAME)
create_project_binary()
inject_project_binary()
clean_up_project_binary_file()
Expand All @@ -191,7 +197,7 @@ func create_project_binary() -> void:
func inject_project_binary() -> void:
var output_add_project_binary := []
var _exit_code_add_project_binary := OS.execute(path.pck_tool, ["--pack", path.pck, "--action", "add", "--file", path.project_binary, "--remove-prefix", path.mod_loader_dir], true, output_add_project_binary)
modloaderutils.log_debug_json_print("Adding custom project.binary to res://", output_add_project_binary, LOG_NAME)
ModLoaderSetupLog.debug_json_print("Adding custom project.binary to res://", output_add_project_binary, LOG_NAME)


# Removes the project.binary file
Expand All @@ -205,27 +211,28 @@ func setup_file_data() -> void:
# C:/path/to/game/game.exe
path.exe = OS.get_executable_path()
# C:/path/to/game/
path.game_base_dir = modloaderutils.get_local_folder_dir()
path.game_base_dir = ModLoaderSetupUtils.get_local_folder_dir()
# C:/path/to/game/addons/mod_loader
path.mod_loader_dir = path.game_base_dir + "addons/mod_loader/"
# C:/path/to/game/addons/mod_loader/vendor/godotpcktool/godotpcktool.exe
path.pck_tool = path.mod_loader_dir + "vendor/godotpcktool/godotpcktool.exe"
# can be supplied to override the exe_name
file_name.cli_arg_exe = modloaderutils.get_cmd_line_arg_value("--exe-name")
file_name.cli_arg_exe = ModLoaderSetupUtils.get_cmd_line_arg_value("--exe-name")
# can be supplied to override the pck_name
file_name.cli_arg_pck = modloaderutils.get_cmd_line_arg_value("--pck-name")
file_name.cli_arg_pck = ModLoaderSetupUtils.get_cmd_line_arg_value("--pck-name")
# game - or use the value of cli_arg_exe_name if there is one
file_name.exe = modloaderutils.get_file_name_from_path(path.exe, true, true) if file_name.cli_arg_exe == '' else file_name.cli_arg_exe
file_name.exe = ModLoaderSetupUtils.get_file_name_from_path(path.exe, true, true) if file_name.cli_arg_exe == '' else file_name.cli_arg_exe
# game - or use the value of cli_arg_pck_name if there is one
# using exe_path.get_file() instead of exe_name
# so you don't override the pck_name with the --exe-name cli arg
# the main pack name is the same as the .exe name
# if --main-pack cli arg is not set
file_name.pck = modloaderutils.get_file_name_from_path(path.exe, true, true) if file_name.cli_arg_pck == '' else file_name.cli_arg_pck
file_name.pck = ModLoaderSetupUtils.get_file_name_from_path(path.exe, true, true) if file_name.cli_arg_pck == '' else file_name.cli_arg_pck
# C:/path/to/game/game.pck
path.pck = path.game_base_dir.plus_file(file_name.pck + '.pck')
# C:/path/to/game/addons/mod_loader/project.binary
path.project_binary = path.mod_loader_dir + "project.binary"

modloaderutils.log_debug_json_print("path: ", path, LOG_NAME)
modloaderutils.log_debug_json_print("file_name: ", file_name, LOG_NAME)
ModLoaderSetupLog.debug_json_print("path: ", path, LOG_NAME)
ModLoaderSetupLog.debug_json_print("file_name: ", file_name, LOG_NAME)

213 changes: 213 additions & 0 deletions addons/mod_loader/setup/setup_log.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
class_name ModLoaderSetupLog


# Slimed down version of ModLoaderLog for the ModLoader Self Setup

const MOD_LOG_PATH := "user://logs/modloader.log"

enum VERBOSITY_LEVEL {
ERROR,
WARNING,
INFO,
DEBUG,
}


class ModLoaderLogEntry:
extends Resource

var mod_name: String
var message: String
var type: String
var time: String


func _init(_mod_name: String, _message: String, _type: String, _time: String) -> void:
mod_name = _mod_name
message = _message
type = _type
time = _time


func get_entry() -> String:
return time + get_prefix() + message


func get_prefix() -> String:
return "%s %s: " % [type.to_upper(), mod_name]


func get_md5() -> String:
return str(get_prefix(), message).md5_text()


# API log functions
# =============================================================================

# Logs the error in red and a stack trace. Prefixed FATAL-ERROR
# Stops the execution in editor
# Always logged
static func fatal(message: String, mod_name: String) -> void:
_log(message, mod_name, "fatal-error")


# Logs the message and pushed an error. Prefixed ERROR
# Always logged
static func error(message: String, mod_name: String) -> void:
_log(message, mod_name, "error")


# Logs the message and pushes a warning. Prefixed WARNING
# Logged with verbosity level at or above warning (-v)
static func warning(message: String, mod_name: String) -> void:
_log(message, mod_name, "warning")


# Logs the message. Prefixed INFO
# Logged with verbosity level at or above info (-vv)
static func info(message: String, mod_name: String) -> void:
_log(message, mod_name, "info")


# Logs the message. Prefixed SUCCESS
# Logged with verbosity level at or above info (-vv)
static func success(message: String, mod_name: String) -> void:
_log(message, mod_name, "success")


# Logs the message. Prefixed DEBUG
# Logged with verbosity level at or above debug (-vvv)
static func debug(message: String, mod_name: String) -> void:
_log(message, mod_name, "debug")


# Logs the message formatted with [method JSON.print]. Prefixed DEBUG
# Logged with verbosity level at or above debug (-vvv)
static func debug_json_print(message: String, json_printable, mod_name: String) -> void:
message = "%s\n%s" % [message, JSON.print(json_printable, " ")]
_log(message, mod_name, "debug")


# Internal log functions
# =============================================================================

static func _log(message: String, mod_name: String, log_type: String = "info") -> void:
var time := "%s " % _get_time_string()
var log_entry := ModLoaderLogEntry.new(mod_name, message, log_type, time)

match log_type.to_lower():
"fatal-error":
push_error(message)
_write_to_log_file(log_entry.get_entry())
_write_to_log_file(JSON.print(get_stack(), " "))
assert(false, message)
"error":
printerr(message)
push_error(message)
_write_to_log_file(log_entry.get_entry())
"warning":
print(log_entry.get_prefix() + message)
push_warning(message)
_write_to_log_file(log_entry.get_entry())
"info", "success":
print(log_entry.get_prefix() + message)
_write_to_log_file(log_entry.get_entry())
"debug":
print(log_entry.get_prefix() + message)
_write_to_log_file(log_entry.get_entry())


# Internal Date Time
# =============================================================================

# Returns the current time as a string in the format hh:mm:ss
static func _get_time_string() -> String:
var date_time := Time.get_datetime_dict_from_system()
return "%02d:%02d:%02d" % [ date_time.hour, date_time.minute, date_time.second ]


# Returns the current date as a string in the format yyyy-mm-dd
static func _get_date_string() -> String:
var date_time := Time.get_datetime_dict_from_system()
return "%s-%02d-%02d" % [ date_time.year, date_time.month, date_time.day ]


# Returns the current date and time as a string in the format yyyy-mm-dd_hh:mm:ss
static func _get_date_time_string() -> String:
return "%s_%s" % [ _get_date_string(), _get_time_string() ]


# Internal File
# =============================================================================

static func _write_to_log_file(string_to_write: String) -> void:
var log_file := File.new()

if not log_file.file_exists(MOD_LOG_PATH):
_rotate_log_file()

var error := log_file.open(MOD_LOG_PATH, File.READ_WRITE)
if not error == OK:
assert(false, "Could not open log file, error code: %s" % error)
return

log_file.seek_end()
log_file.store_string("\n" + string_to_write)
log_file.close()


# Keeps log backups for every run, just like the Godot; gdscript implementation of
# https://github.com/godotengine/godot/blob/1d14c054a12dacdc193b589e4afb0ef319ee2aae/core/io/logger.cpp#L151
static func _rotate_log_file() -> void:
var MAX_LOGS := int(ProjectSettings.get_setting("logging/file_logging/max_log_files"))
var log_file := File.new()

if log_file.file_exists(MOD_LOG_PATH):
if MAX_LOGS > 1:
var datetime := _get_date_time_string().replace(":", ".")
var backup_name: String = MOD_LOG_PATH.get_basename() + "_" + datetime
if MOD_LOG_PATH.get_extension().length() > 0:
backup_name += "." + MOD_LOG_PATH.get_extension()

var dir := Directory.new()
if dir.dir_exists(MOD_LOG_PATH.get_base_dir()):
dir.copy(MOD_LOG_PATH, backup_name)
_clear_old_log_backups()

# only File.WRITE creates a new file, File.READ_WRITE throws an error
var error := log_file.open(MOD_LOG_PATH, File.WRITE)
if not error == OK:
assert(false, "Could not open log file, error code: %s" % error)
log_file.store_string('%s Created log' % _get_date_string())
log_file.close()


static func _clear_old_log_backups() -> void:
var MAX_LOGS := int(ProjectSettings.get_setting("logging/file_logging/max_log_files"))
var MAX_BACKUPS := MAX_LOGS - 1 # -1 for the current new log (not a backup)
var basename := MOD_LOG_PATH.get_file().get_basename() as String
var extension := MOD_LOG_PATH.get_extension() as String

var dir := Directory.new()
if not dir.dir_exists(MOD_LOG_PATH.get_base_dir()):
return
if not dir.open(MOD_LOG_PATH.get_base_dir()) == OK:
return

dir.list_dir_begin()
var file := dir.get_next()
var backups := []
while file.length() > 0:
if (not dir.current_is_dir() and
file.begins_with(basename) and
file.get_extension() == extension and
not file == MOD_LOG_PATH.get_file()):
backups.append(file)
file = dir.get_next()
dir.list_dir_end()

if backups.size() > MAX_BACKUPS:
backups.sort()
backups.resize(backups.size() - MAX_BACKUPS)
for file_to_delete in backups:
dir.remove(file_to_delete)
Loading

0 comments on commit 5cff450

Please sign in to comment.