Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_ENGINE_DEBUGGING_AGS_EDITOR_DEBUGGER_H
#define AGS_ENGINE_DEBUGGING_AGS_EDITOR_DEBUGGER_H
namespace AGS3 {
struct IAGSEditorDebugger {
public:
virtual ~IAGSEditorDebugger() {}
virtual bool Initialize() = 0;
virtual void Shutdown() = 0;
virtual bool SendMessageToEditor(const char *message) = 0;
virtual bool IsMessageAvailable() = 0;
// Message will be allocated on heap with malloc
virtual char *GetNextMessage() = 0;
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,534 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/memory.h"
#include "common/std/limits.h"
#include "common/std/initializer_list.h"
#include "ags/shared/core/platform.h"
#include "ags/shared/ac/common.h"
#include "ags/shared/ac/game_setup_struct.h"
#include "ags/engine/ac/game_state.h"
#include "ags/engine/ac/runtime_defines.h"
#include "ags/engine/debugging/ags_editor_debugger.h"
#include "ags/engine/debugging/debug_log.h"
#include "ags/engine/debugging/debugger.h"
#include "ags/shared/debugging/debug_manager.h"
#include "ags/shared/debugging/out.h"
#include "ags/engine/debugging/log_file.h"
#include "ags/engine/debugging/message_buffer.h"
#include "ags/engine/main/config.h"
#include "ags/engine/main/game_run.h"
#include "ags/engine/media/audio/audio_system.h"
#include "ags/engine/platform/base/ags_platform_driver.h"
#include "ags/engine/platform/base/sys_main.h"
#include "ags/plugins/plugin_engine.h"
#include "ags/engine/script/script.h"
#include "ags/shared/script/cc_internal.h"
#include "ags/shared/script/cc_common.h"
#include "ags/shared/util/path.h"
#include "ags/shared/util/string_utils.h"
#include "ags/shared/util/text_stream_writer.h"
#include "ags/globals.h"
#if AGS_PLATFORM_OS_WINDOWS
#include "ags/shared/platform/windows/debug/namedpipesagsdebugger.h"
#endif
namespace AGS3 {
using namespace AGS::Shared;
using namespace AGS::Engine;
#if AGS_PLATFORM_OS_WINDOWS
HWND editor_window_handle = 0;
IAGSEditorDebugger *GetEditorDebugger(const char *instanceToken) {
return new NamedPipesAGSDebugger(instanceToken);
}
#else // AGS_PLATFORM_OS_WINDOWS
IAGSEditorDebugger *GetEditorDebugger(const char * /*instanceToken*/) {
return nullptr;
}
#endif
void send_message_to_debugger(const std::vector<std::pair<String, String> > &tag_values, const String &command) {
String messageToSend = String::FromFormat(R"(<?xml version=" 1.0 " encoding=" Windows - 1252 "?><Debugger Command=" % s ">)", command.GetCStr());
#if AGS_PLATFORM_OS_WINDOWS
messageToSend.Append(String::FromFormat(" <EngineWindow>%d</EngineWindow> ", (int)sys_win_get_window()));
#endif
for (const auto &tag_value : tag_values) {
messageToSend.AppendFmt(" <%s><![CDATA[%s]]></%s> ", tag_value.first.GetCStr(), tag_value.second.GetCStr(), tag_value.first.GetCStr());
}
messageToSend.Append("</Debugger>\n");
_G(editor_debugger)->SendMessageToEditor(messageToSend.GetCStr());
}
static const char *OutputMsgBufID = "buffer";
static const char *OutputFileID = "file";
static const char *OutputSystemID = "stdout";
// Create a new log output by ID
PDebugOutput create_log_output(const String &name, const String &dir = "", const String &filename = "", LogFile::OpenMode open_mode = LogFile::kLogFile_Overwrite) {
// Else create new one, if we know this ID
if (name.CompareNoCase(OutputSystemID) == 0) {
return _GP(DbgMgr).RegisterOutput(OutputSystemID, AGSPlatformDriver::GetDriver(), kDbgMsg_None);
} else if (name.CompareNoCase(OutputFileID) == 0) {
_GP(DebugLogFile).reset(new LogFile());
String logfile_dir = dir;
if (dir.IsEmpty()) {
FSLocation fs = _G(platform)->GetAppOutputDirectory();
CreateFSDirs(fs);
logfile_dir = fs.FullDir;
} else if (Path::IsRelativePath(dir) && _G(platform)->IsLocalDirRestricted()) {
FSLocation fs = GetGameUserDataDir();
CreateFSDirs(fs);
logfile_dir = fs.FullDir;
}
String logfilename = filename.IsEmpty() ? "ags.log" : filename;
#if AGS_PLATFORM_SCUMMVM
logfile_dir = ""; // ignore path
#endif
String logfile_path = Path::ConcatPaths(logfile_dir, logfilename);
if (!_GP(DebugLogFile)->OpenFile(logfile_path, open_mode))
return nullptr;
Debug::Printf(kDbgMsg_Info, "Logging to %s", logfile_path.GetCStr());
auto dbgout = _GP(DbgMgr).RegisterOutput(OutputFileID, _GP(DebugLogFile).get(), kDbgMsg_None);
return dbgout;
}
return nullptr;
}
// Parses a string where each character defines a single log group; returns list of real group names.
std::vector<String> parse_log_multigroup(const String &group_str) {
std::vector<String> grplist;
for (size_t i = 0; i < group_str.GetLength(); ++i) {
switch (group_str[i]) {
case 'm':
grplist.emplace_back("main");
break;
case 'g':
grplist.emplace_back("game");
break;
case 's':
grplist.emplace_back("script");
break;
case 'c':
grplist.emplace_back("sprcache");
break;
case 'o':
grplist.emplace_back("manobj");
break;
}
}
return grplist;
}
MessageType get_messagetype_from_string(const String &mt) {
int mtype;
if (StrUtil::StringToInt(mt, mtype, 0) == StrUtil::kNoError)
return (MessageType)mtype;
if (mt.CompareNoCase("alert") == 0) return kDbgMsg_Alert;
else if (mt.CompareNoCase("fatal") == 0) return kDbgMsg_Fatal;
else if (mt.CompareNoCase("error") == 0) return kDbgMsg_Error;
else if (mt.CompareNoCase("warn") == 0) return kDbgMsg_Warn;
else if (mt.CompareNoCase("info") == 0) return kDbgMsg_Info;
else if (mt.CompareNoCase("debug") == 0) return kDbgMsg_Debug;
else if (mt.CompareNoCase("all") == 0) return kDbgMsg_All;
return kDbgMsg_None;
}
typedef std::pair<CommonDebugGroup, MessageType> DbgGroupOption;
void apply_log_config(const ConfigTree &cfg, const String &log_id,
bool def_enabled,
std::initializer_list<DbgGroupOption> def_opts) {
String value = CfgReadString(cfg, "log", log_id);
if (value.IsEmpty() && !def_enabled)
return;
// First test if already registered, if not then try create it
auto dbgout = _GP(DbgMgr).GetOutput(log_id);
const bool was_created_earlier = dbgout != nullptr;
if (!dbgout) {
String path = CfgReadString(cfg, "log", String::FromFormat("%s-path", log_id.GetCStr()));
dbgout = create_log_output(log_id, path);
if (!dbgout)
return; // unknown output type
}
dbgout->ClearGroupFilters();
if (value.IsEmpty() || value.CompareNoCase("default") == 0) {
for (const auto &opt : def_opts)
dbgout->SetGroupFilter(opt.first, opt.second);
} else {
const auto options = value.Split(',');
for (const auto &opt : options) {
String groupname = opt.LeftSection(':');
MessageType msgtype = kDbgMsg_All;
if (opt.GetLength() >= groupname.GetLength() + 1) {
String msglevel = opt.Mid(groupname.GetLength() + 1);
msglevel.Trim();
if (msglevel.GetLength() > 0)
msgtype = get_messagetype_from_string(msglevel);
}
groupname.Trim();
if (groupname.CompareNoCase("all") == 0 || groupname.IsEmpty()) {
dbgout->SetAllGroupFilters(msgtype);
} else if (groupname[0u] != '+') {
dbgout->SetGroupFilter(groupname, msgtype);
} else {
const auto groups = parse_log_multigroup(groupname);
for (const auto &g : groups)
dbgout->SetGroupFilter(g, msgtype);
}
}
}
// Delegate buffered messages to this new output
if (_GP(DebugMsgBuff) && !was_created_earlier)
_GP(DebugMsgBuff)->Send(log_id);
}
void init_debug(const ConfigTree &cfg, bool stderr_only) {
// Register outputs
apply_debug_config(cfg);
_G(platform)->SetOutputToErr(stderr_only);
if (stderr_only)
return;
// Message buffer to save all messages in case we read different log settings from config file
_GP(DebugMsgBuff).reset(new MessageBuffer());
_GP(DbgMgr).RegisterOutput(OutputMsgBufID, _GP(DebugMsgBuff).get(), kDbgMsg_All);
}
void apply_debug_config(const ConfigTree &cfg) {
apply_log_config(cfg, OutputSystemID, /* defaults */ true, { DbgGroupOption(kDbgGroup_Main, kDbgMsg_Info) });
bool legacy_log_enabled = CfgReadBoolInt(cfg, "misc", "log", false);
apply_log_config(cfg, OutputFileID,
/* defaults */
legacy_log_enabled, {
DbgGroupOption(kDbgGroup_Main, kDbgMsg_All),
DbgGroupOption(kDbgGroup_Game, kDbgMsg_Info),
#if DEBUG_SPRITECACHE
DbgGroupOption(kDbgGroup_SprCache, kDbgMsg_All),
#else
DbgGroupOption(kDbgGroup_SprCache, kDbgMsg_Info),
#endif
#if DEBUG_MANAGED_OBJECTS
DbgGroupOption(kDbgGroup_ManObj, kDbgMsg_All),
#else
DbgGroupOption(kDbgGroup_ManObj, kDbgMsg_Info),
#endif
});
// If the game was compiled in Debug mode *and* there's no regular file log,
// then open "warnings.log" for printing script warnings.
if (_GP(game).options[OPT_DEBUGMODE] != 0 && !_GP(DebugLogFile)) {
auto dbgout = create_log_output(OutputFileID, "./", "warnings.log", LogFile::kLogFile_OverwriteAtFirstMessage);
if (dbgout)
dbgout->SetGroupFilter(kDbgGroup_Game, kDbgMsg_Warn);
}
// We don't need message buffer beyond this point
_GP(DbgMgr).UnregisterOutput(OutputMsgBufID);
_GP(DebugMsgBuff).reset();
}
void shutdown_debug() {
// Shutdown output subsystem
_GP(DbgMgr).UnregisterAll();
_GP(DebugMsgBuff).reset();
_GP(DebugLogFile).reset();
}
// Prepends message text with current room number and running script info, then logs result
static void debug_script_print_impl(const String &msg, MessageType mt) {
String script_ref;
ccInstance *curinst = ccInstance::GetCurrentInstance();
if (curinst != nullptr) {
String scriptname;
if (curinst->instanceof == _GP(gamescript))
scriptname = "G ";
else if (curinst->instanceof == _GP(thisroom).CompiledScript)
scriptname = "R ";
else if (curinst->instanceof == _GP(dialogScriptsScript))
scriptname = "D ";
else
scriptname = "? ";
script_ref.Format("[%s%d]", scriptname.GetCStr(), _G(currentline));
}
Debug::Printf(kDbgGroup_Game, mt, "(room:%d)%s %s", _G(displayed_room), script_ref.GetCStr(), msg.GetCStr());
}
void debug_script_print(MessageType mt, const char *msg, ...) {
va_list ap;
va_start(ap, msg);
String full_msg = String::FromFormatV(msg, ap);
va_end(ap);
debug_script_print_impl(full_msg, mt);
}
void debug_script_warn(const char *msg, ...) {
va_list ap;
va_start(ap, msg);
String full_msg = String::FromFormatV(msg, ap);
va_end(ap);
debug_script_print_impl(full_msg, kDbgMsg_Warn);
}
void debug_script_log(const char *msg, ...) {
va_list ap;
va_start(ap, msg);
String full_msg = String::FromFormatV(msg, ap);
va_end(ap);
debug_script_print_impl(full_msg, kDbgMsg_Debug);
}
struct Breakpoint {
char scriptName[80]{};
int lineNumber = 0;
};
bool send_state_to_debugger(const String& msg, const String& errorMsg) {
// Get either saved callstack from a script error, or current execution point
String callStack = (!errorMsg.IsEmpty() && cc_has_error()) ?
cc_get_error().CallStack : cc_get_callstack();
if (callStack.IsEmpty())
return false;
std::vector<std::pair<String, String>> script_info = {{"ScriptState", callStack}};
if (!errorMsg.IsEmpty()) {
script_info.emplace_back("ErrorMessage", errorMsg);
}
send_message_to_debugger(script_info, msg);
return true;
}
bool send_state_to_debugger(const char *msg) {
return send_state_to_debugger(String(msg), String());
}
bool init_editor_debugging() {
#if AGS_PLATFORM_OS_WINDOWS
_G(editor_debugger) = GetEditorDebugger(_G(editor_debugger_instance_token));
#else
// Editor isn't ported yet
_G(editor_debugger) = nullptr;
#endif
if (_G(editor_debugger) == nullptr)
quit("editor_debugger is NULL but debugger enabled");
if (_G(editor_debugger)->Initialize()) {
_G(editor_debugging_initialized) = 1;
// Wait for the editor to send the initial breakpoints
// and then its READY message
while (check_for_messages_from_debugger() != 2) {
_G(platform)->Delay(10);
}
send_state_to_debugger("START");
Debug::Printf(kDbgMsg_Info, "External debugger initialized");
return true;
}
Debug::Printf(kDbgMsg_Error, "Failed to initialize external debugger");
return false;
}
int check_for_messages_from_debugger() {
if (_G(editor_debugger)->IsMessageAvailable()) {
char *msg = _G(editor_debugger)->GetNextMessage();
if (msg == nullptr) {
return 0;
}
if (strncmp(msg, "<Engine Command=\"", 17) != 0) {
//Debug::Printf("Faulty message received from editor:");
//Debug::Printf(msg);
free(msg);
return 0;
}
const char *msgPtr = &msg[17];
if (strncmp(msgPtr, "START", 5) == 0) {
#if AGS_PLATFORM_OS_WINDOWS
const char *windowHandle = strstr(msgPtr, "EditorWindow") + 14;
editor_window_handle = (HWND)atoi(windowHandle);
#endif
} else if (strncmp(msgPtr, "READY", 5) == 0) {
free(msg);
return 2;
} else if ((strncmp(msgPtr, "SETBREAK", 8) == 0) ||
(strncmp(msgPtr, "DELBREAK", 8) == 0)) {
bool isDelete = (msgPtr[0] == 'D');
// Format: SETBREAK $scriptname$lineNumber$
msgPtr += 10;
char scriptNameBuf[sizeof(Breakpoint::scriptName)]{};
for (size_t i = 0; msgPtr[0] != '$'; ++msgPtr, ++i) {
if (i < sizeof(scriptNameBuf) - 1)
scriptNameBuf[i] = msgPtr[0];
}
msgPtr++;
int lineNumber = atoi(msgPtr);
if (isDelete) {
for (size_t i = 0; i < _G(breakpoints).size(); ++i) {
if ((_G(breakpoints)[i].lineNumber == lineNumber) &&
(strcmp(_G(breakpoints)[i].scriptName, scriptNameBuf) == 0)) {
_G(breakpoints).erase(_G(breakpoints).begin() + i);
break;
}
}
} else {
Globals::Breakpoint bp;
snprintf(bp.scriptName, sizeof(Breakpoint::scriptName), "%s", scriptNameBuf);
bp.lineNumber = lineNumber;
_G(breakpoints).push_back(bp);
}
} else if (strncmp(msgPtr, "RESUME", 6) == 0) {
_G(game_paused_in_debugger) = 0;
} else if (strncmp(msgPtr, "STEP", 4) == 0) {
_G(game_paused_in_debugger) = 0;
_G(break_on_next_script_step) = 1;
} else if (strncmp(msgPtr, "EXIT", 4) == 0) {
_G(want_exit) = true;
_G(abort_engine) = true;
_G(check_dynamic_sprites_at_exit) = 0;
}
free(msg);
return 1;
}
return 0;
}
bool send_exception_to_debugger(const char *qmsg) {
#if AGS_PLATFORM_OS_WINDOWS
_G(want_exit) = false;
// allow the editor to break with the error message
if (editor_window_handle != NULL)
SetForegroundWindow(editor_window_handle);
if (!send_state_to_debugger("ERROR", qmsg))
return false;
while ((check_for_messages_from_debugger() == 0) && (!_G(want_exit))) {
_G(platform)->Delay(10);
}
#endif
return true;
}
void break_into_debugger() {
#if AGS_PLATFORM_OS_WINDOWS
if (editor_window_handle != NULL)
SetForegroundWindow(editor_window_handle);
send_state_to_debugger("BREAK");
_G(game_paused_in_debugger) = 1;
while (_G(game_paused_in_debugger)) {
update_polled_stuff();
_G(platform)->YieldCPU();
}
#endif
}
int scrDebugWait = 0;
// allow LShift to single-step, RShift to pause flow
void scriptDebugHook(ccInstance *ccinst, int linenum) {
if (_G(pluginsWantingDebugHooks) > 0) {
// a plugin is handling the debugging
String scname = GetScriptName(ccinst);
pl_run_plugin_debug_hooks(scname.GetCStr(), linenum);
return;
}
// no plugin, use built-in debugger
if (ccinst == nullptr) {
// come out of script
return;
}
if (_G(break_on_next_script_step)) {
_G(break_on_next_script_step) = 0;
break_into_debugger();
return;
}
const char *scriptName = ccinst->runningInst->instanceof->GetSectionName(ccinst->pc);
for (const auto &breakpoint : _G(breakpoints)) {
if ((breakpoint.lineNumber == linenum) && (strcmp(breakpoint.scriptName, scriptName) == 0)) {
break_into_debugger();
break;
}
}
}
int scrlockWasDown = 0;
void check_debug_keys() {
#ifdef TODO
if (_GP(play).debug_mode) {
// do the run-time script debugging
const Uint8 *ks = SDL_GetKeyboardState(nullptr);
if ((!ks[SDL_SCANCODE_SCROLLLOCK]) && (scrlockWasDown))
scrlockWasDown = 0;
else if ((ks[SDL_SCANCODE_SCROLLLOCK]) && (!scrlockWasDown)) {
_G(break_on_next_script_step) = 1;
scrlockWasDown = 1;
}
}
#endif
}
} // namespace AGS3

View File

@@ -0,0 +1,56 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_ENGINE_DEBUGGING_DEBUG_LOG_H
#define AGS_ENGINE_DEBUGGING_DEBUG_LOG_H
#include "ags/engine/ac/runtime_defines.h"
#include "ags/shared/ac/common.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/util/ini_util.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
struct ccInstance;
void init_debug(const AGS::Shared::ConfigTree &cfg, bool stderr_only);
void apply_debug_config(const AGS::Shared::ConfigTree &cfg);
void shutdown_debug();
// prints debug messages of given type tagged with kDbgGroup_Game,
// prepending it with current room number and script position info
void debug_script_print(AGS::Shared::MessageType mt, const char *msg, ...);
// prints formatted debug warnings tagged with kDbgGroup_Game,
// prepending it with current room number and script position info
void debug_script_warn(const char *msg, ...);
// prints formatted debug message tagged with kDbgGroup_Game,
// prepending it with current room number and script position info
void debug_script_log(const char *msg, ...);
// Connect engine to external debugger, if one is available
bool init_editor_debugging();
// allow LShift to single-step, RShift to pause flow
void scriptDebugHook(ccInstance *ccinst, int linenum);
} // namespace AGS3
#endif

View File

@@ -0,0 +1,59 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_ENGINE_DEBUGGING_DEBUGGER_H
#define AGS_ENGINE_DEBUGGING_DEBUGGER_H
#include "ags/shared/util/string.h"
namespace AGS3 {
struct IAGSEditorDebugger;
struct ScriptPosition;
int check_for_messages_from_debugger();
bool send_state_to_debugger(const char *msg);
bool send_exception_to_debugger(const char *qmsg);
// Returns current script's location and callstack
AGS::Shared::String get_cur_script(int numberOfLinesOfCallStack);
void check_debug_keys();
#define DBG_NOIFACE 1
#define DBG_NODRAWSPRITES 2
#define DBG_NOOBJECTS 4
#define DBG_NOUPDATE 8
#define DBG_NOSFX 0x10
#define DBG_NOMUSIC 0x20
#define DBG_NOSCRIPT 0x40
// #define DBG_DBGSCRIPT 0x80 // unused
#define DBG_DEBUGMODE 0x100
#define DBG_REGONLY 0x200
#define DBG_NOVIDEO 0x400
enum FPSDisplayMode {
kFPS_Hide = 0, // hid by the script/user command
kFPS_Display = 1, // shown by the script/user command
kFPS_Forced = 2 // forced shown by the engine arg
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,50 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_ENGINE_DEBUGGING_DUMMY_AGS_DEBUGGER_H
#define AGS_ENGINE_DEBUGGING_DUMMY_AGS_DEBUGGER_H
#include "ags/engine/debugging/debugger.h"
namespace AGS3 {
struct DummyAGSDebugger : IAGSEditorDebugger {
public:
bool Initialize() override {
return false;
}
void Shutdown() override {
}
bool SendMessageToEditor(const char *message) override {
return false;
}
bool IsMessageAvailable() override {
return false;
}
char *GetNextMessage() override {
return NULL;
}
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,79 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/engine/debugging/file_based_ags_debugger.h"
#include "ags/shared/util/file.h"
#include "ags/shared/util/path.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/text_stream_writer.h"
#include "ags/engine/platform/base/ags_platform_driver.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
const char *SENT_MESSAGE_FILE_NAME = "dbgrecv.tmp";
bool FileBasedAGSDebugger::Initialize() {
if (File::IsFile(SENT_MESSAGE_FILE_NAME)) {
File::DeleteFile(SENT_MESSAGE_FILE_NAME);
}
return true;
}
void FileBasedAGSDebugger::Shutdown() {
}
bool FileBasedAGSDebugger::SendMessageToEditor(const char *message) {
while (File::IsFile(SENT_MESSAGE_FILE_NAME)) {
_G(platform)->YieldCPU();
}
Stream *out = Shared::File::CreateFile(SENT_MESSAGE_FILE_NAME);
// CHECKME: originally the file was opened as "wb" for some reason,
// which means the message should be written as a binary array;
// or shouldn't it?
out->Write(message, strlen(message));
delete out;
return true;
}
bool FileBasedAGSDebugger::IsMessageAvailable() {
return (File::IsFile("dbgsend.tmp") != 0);
}
char *FileBasedAGSDebugger::GetNextMessage() {
Stream *in = Shared::File::OpenFileRead("dbgsend.tmp");
if (in == nullptr) {
// check again, because the editor might have deleted the file in the meantime
return nullptr;
}
soff_t fileSize = in->GetLength();
char *msg = (char *)malloc(fileSize + 1);
in->Read(msg, fileSize);
delete in;
File::DeleteFile("dbgsend.tmp");
msg[fileSize] = 0;
return msg;
}
} // namespace AGS3

View File

@@ -0,0 +1,44 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_ENGINE_DEBUGGING_FILE_BASED_AGS_DEBUGGER_H
#define AGS_ENGINE_DEBUGGING_FILE_BASED_AGS_DEBUGGER_H
#include "ags/engine/debugging/ags_editor_debugger.h"
namespace AGS3 {
struct FileBasedAGSDebugger : IAGSEditorDebugger {
public:
bool Initialize() override;
void Shutdown() override;
bool SendMessageToEditor(const char *message) override;
bool IsMessageAvailable() override;
char *GetNextMessage() override;
};
extern const char *SENT_MESSAGE_FILE_NAME;
} // namespace AGS3
#endif

View File

@@ -0,0 +1,83 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/engine/debugging/log_file.h"
#include "ags/shared/util/file.h"
#include "ags/shared/util/stream.h"
namespace AGS3 {
namespace AGS {
namespace Engine {
using namespace Shared;
LogFile::LogFile()
: _openMode(kLogFile_Overwrite) {
}
void LogFile::PrintMessage(const DebugMessage &msg) {
if (!_file.get()) {
if (_filePath.IsEmpty())
return;
_file.reset(File::OpenFile(_filePath, _openMode == kLogFile_Append ? Shared::kFile_Create : Shared::kFile_CreateAlways,
Shared::kFile_Write));
if (!_file) {
Debug::Printf("Unable to write log to '%s'.", _filePath.GetCStr());
_filePath = "";
return;
}
}
if (!msg.GroupName.IsEmpty()) {
_file->Write(msg.GroupName.GetCStr(), msg.GroupName.GetLength());
_file->Write(" : ", 3);
}
_file->Write(msg.Text.GetCStr(), msg.Text.GetLength());
_file->WriteInt8('\n');
// We should flush after every write to the log; this will make writing
// bit slower, but will increase the chances that all latest output
// will get to the disk in case of program crash.
_file->Flush();
}
bool LogFile::OpenFile(const String &file_path, OpenMode open_mode) {
CloseFile();
_filePath = file_path;
_openMode = open_mode;
if (open_mode == OpenMode::kLogFile_OverwriteAtFirstMessage) {
return File::TestWriteFile(_filePath);
} else {
_file.reset(File::OpenFile(file_path,
open_mode == kLogFile_Append ? Shared::kFile_Create : Shared::kFile_CreateAlways,
Shared::kFile_Write));
return _file.get() != nullptr;
}
}
void LogFile::CloseFile() {
_file.reset();
_filePath.Empty();
}
} // namespace Engine
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,88 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// LogFile, the IOutputHandler implementation that writes to file.
//
// When created LogFile may open file right away or delay doing this.
// In the latter case it will buffer output up to certain size limit.
// When told to open the file, it will first flush its buffer. This allows to
// log events even before the log path is decided (for example, before or
// during reading configuration and/or parsing command line).
//
//=============================================================================
#ifndef AGS_ENGINE_DEBUGGING_LOG_FILE_H
#define AGS_ENGINE_DEBUGGING_LOG_FILE_H
#include "common/std/memory.h"
#include "ags/shared/debugging/output_handler.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
}
namespace Engine {
using Shared::DebugMessage;
using Shared::Stream;
using Shared::String;
class LogFile : public AGS::Shared::IOutputHandler {
public:
enum OpenMode {
kLogFile_Overwrite,
kLogFile_OverwriteAtFirstMessage,
kLogFile_Append
};
public:
LogFile();
void PrintMessage(const Shared::DebugMessage &msg) override;
// Open file using given file path, optionally appending if one exists
//
// TODO: filepath parameter here may be actually used as a pattern
// or prefix, while the actual filename could be made by combining
// this prefix with current date, game name, and similar additional
// useful information. Whether this is to be determined here or on
// high-level side remains a question.
//
bool OpenFile(const String &file_path, OpenMode open_mode = kLogFile_Overwrite);
// Close file
void CloseFile();
private:
std::unique_ptr<Stream> _file;
String _filePath;
OpenMode _openMode;
};
} // namespace Engine
} // namespace AGS
} // namespace AGS3
#endif

View File

@@ -0,0 +1,70 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/debugging/debug_manager.h"
#include "ags/engine/debugging/message_buffer.h"
#include "ags/globals.h"
namespace AGS3 {
namespace AGS {
namespace Engine {
using namespace Shared;
MessageBuffer::MessageBuffer(size_t buffer_limit)
: _bufferLimit(buffer_limit)
, _msgLost(0) {
}
void MessageBuffer::PrintMessage(const DebugMessage &msg) {
if (_buffer.size() < _bufferLimit)
_buffer.push_back(msg);
else
_msgLost++;
}
void MessageBuffer::Clear() {
_buffer.clear();
_msgLost = 0;
}
void MessageBuffer::Send(const String &out_id) {
if (_buffer.empty())
return;
if (_msgLost > 0) {
DebugGroup gr = _GP(DbgMgr).GetGroup(kDbgGroup_Main);
_GP(DbgMgr).SendMessage(out_id, DebugMessage(
String::FromFormat("WARNING: output %s lost exceeding buffer: %zu debug messages\n", out_id.GetCStr(), (unsigned)_msgLost),
gr.UID.ID, gr.OutputName, kDbgMsg_All));
}
for (std::vector<DebugMessage>::const_iterator it = _buffer.begin(); it != _buffer.end(); ++it) {
_GP(DbgMgr).SendMessage(out_id, *it);
}
}
void MessageBuffer::Flush(const String &out_id) {
Send(out_id);
Clear();
}
} // namespace Engine
} // namespace AGS
} // namespace AGS3

View File

@@ -0,0 +1,66 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// MessageBuffer, the IOutputHandler implementation that stores debug messages
// in a vector. Could be handy if you need to temporarily buffer debug log
// while specifying how to actually print it.
//
//=============================================================================
#ifndef AGS_ENGINE_DEBUGGING_MESSAGE_BUFFER_H
#define AGS_ENGINE_DEBUGGING_MESSAGE_BUFFER_H
#include "common/std/vector.h"
#include "ags/shared/debugging/output_handler.h"
namespace AGS3 {
namespace AGS {
namespace Engine {
using Shared::String;
using Shared::DebugMessage;
class MessageBuffer : public AGS::Shared::IOutputHandler {
public:
MessageBuffer(size_t buffer_limit = 1024);
void PrintMessage(const DebugMessage &msg) override;
// Clears buffer
void Clear();
// Sends buffered messages into given output target
void Send(const String &out_id);
// Sends buffered messages into given output target and clears buffer
void Flush(const String &out_id);
private:
const size_t _bufferLimit;
std::vector<DebugMessage> _buffer;
size_t _msgLost;
};
} // namespace Engine
} // namespace AGS
} // namespace AGS3
#endif