g_MantisUrl = "http://mantis.haemimontgames.com" config.BugReporterXTemplateID = "BugReport" local AutoReporter = "8o6myXi_5UcfoF4_qwl4hjAjFX95w8ws" function OnMsg.ClassesBuilt() if not rawget(_G, "HGMembers") then rawset(_G, "HGMembers", {}) end end local function GetReporter(reporter_name) reporter_name = reporter_name or (LocalStorage and LocalStorage.dlgBugReport.reporter) reporter_name = reporter_name and reporter_name ~= "" and reporter_name or config.MantisUser or GetUsername() return HGMembers[reporter_name] and HGMembers[reporter_name].mantis_token or nil, reporter_name end --- Reports a bug in the specified idProject/category with the summary/description given and optional attachment if filename is present. -- Only one bug can be reported at the same time. Opens a message box if error has occured during reporting. -- @cstyle void ReportBug(table bug_data). -- @param bug_data a table containing all bug information: reporter, project ID, category, summary, description, handler, priority, target_version, files -- @return void. function ReportBug(bug_data) local files = {} for _, ftype in ipairs{"image", "log", "save", "autosave"} do local name = bug_data[ftype] if name then local content = bug_data[ftype .. "_pstr"] if not content then local err err, content = AsyncFileToString(name) if err then print("Failed to load file", name, ":", err) end end if content then local path, file, ext = SplitPath(name) files[#files + 1] = { name = file .. ext, content = Encode64(content), } end end end if bug_data.extra_info then files[#files + 1] = { name = "ExtraInfo.txt", content = Encode64(bug_data.extra_info), } end if bug_data.files then for _, filename in ipairs(bug_data.files) do local err, content = AsyncFileToString(filename) if err then print("Failed to load file", filename, ":", err) else table.insert(files, {name = filename, content = Encode64(content)}) end end end local mod = bug_data.mod if mod then local paths = {} if mod.attachModTagAndFiles then paths[#paths+1] = mod.mod_items_path paths[#paths+1] = mod.mod_metadata_path if next(mod.mod_scripts_path) then table.iappend(paths, mod.mod_scripts_path) end end for _, filename in ipairs(paths) do local err, content = AsyncFileToString(filename) if err then DebugPrint("Failed to load file", filename, ":", err) elseif content and content ~= "" then table.insert(files, {name = filename, content = Encode64(content)}) end end if mod.attachMod then DebugPrint("Attaching mod...") DebugPrint(Platform.ged and g_GedApp) if mod.mod_size_check then local err, content if mod.mod_is_packed then err, content = AsyncFileToString(mod.mod_is_packed) else local pack_path = Platform.ged and g_GedApp and g_GedApp:Call("GedPackModForBugReport", mod) or (not Platform.ged and PackModForBugReporter(mod)) DebugPrint(string.format("mod pack path is %s", pack_path)) if pack_path then err, content = AsyncFileToString(pack_path) else err = DebugPrint(string.format("Failed to pack mod and receive path of the pack for: ", mod.title)) end end if not err then table.insert(files, {name = mod.id .. "_" ..ModsPackFileName, content = Encode64(content)}) else DebugPrint(err) end else DebugPrint(string.format("Failed to upload mod '%s'. Reason: size over 100MB", mod.title)) end end end local description = bug_data.reporter == AutoReporter and bug_data.description .. "\n\n" .. bug_data.note or bug_data.description description = string.trim_spaces(description or "") print("Reporting...") local headers = {Authorization = bug_data.reporter, ["Content-Type"] = "application/json"} local err, info if bug_data.appendToBug then -- Append the gathered data as a note to an existing issue local note_text = bug_data.summary .. "\n\n" .. description if bug_data.reporter ~= AutoReporter and (bug_data.note or "") ~= "" then note_text = note_text .. "\n\n" .. bug_data.note end note_text = note_text .. "\n" local body = { text = note_text, view_state = { name = "public" }, files = files, } local json_err, json_body = LuaToJSON(body) err, info = AsyncWebRequest{ method = "POST", url = g_MantisUrl .. "/api/rest/issues/" .. tostring(bug_data.appendToBug) .. "/notes", headers = headers, body = json_body, timeout = 60000, } else -- Create a new issue local body = { project = {id = bug_data.project}, category = {name = bug_data.category}, summary = bug_data.summary, description = #description > 0 and description or ".", handler = {id = bug_data.handler}, priority = {id = bug_data.priority}, severity = {id = bug_data.severity}, reproducibility = {id = bug_data.reproducibility}, target_version = bug_data.target_version, files = files, } local json_err, json_body = LuaToJSON(body) err, info = AsyncWebRequest{ method = "POST", url = g_MantisUrl .. "/api/rest/issues/", headers = headers, body = json_body, timeout = 60000, } end if not err and type(info) == "string" then local idx1, idx2 = string.find_lower(info, "error") if idx2 then local idx3 = string.find(info, ".", idx2 + 1, true) local err_str = string.sub(info, idx2 + 1, idx3 - 1) err = string.match(err_str, "[%w%s()_]+$") end end if err then local json_err, luaInfo = JSONToLua(info) if err == 403 then if luaInfo then return string.format("Invalid API Token: %d(%s)\n%s", luaInfo.code, luaInfo.localized, luaInfo.message) else return "Invalid API Token!" end elseif err == 401 then return "API Token is missing" elseif err == 404 then return string.format("Not Found\nCode: %d\nMessage: %s", luaInfo and luaInfo.code or "Missing code", luaInfo and luaInfo.message or "Missing message") elseif err ~= 201 then print("Failed to report the bug! \nURL:", g_MantisUrl, "\nError:", err, "\nInfo:", ValueToLuaCode(info)) if err == true then return "Couldn't receive information from the server. Please check your internet connection" end return tostring(err) .. " " .. ValueToLuaCode(json_err and luaInfo or info) end end local err, luaInfo = JSONToLua(info) local issueId = bug_data.appendToBug or (luaInfo and luaInfo.issue and luaInfo.issue.id) if not bug_data.appendToBug then if bug_data.reporter ~= AutoReporter and (bug_data.note or "") ~= "" then Sleep(1000) local body = {text = bug_data.note, view_state = {name = "public"}} local err, body = LuaToJSON(body) local err, info = AsyncWebRequest{ method = "POST", url = g_MantisUrl .. "/api/rest/issues/" .. tostring(issueId) .. "/notes", headers = headers, body = body, timeout = 60000, } end if bug_data.tags then Sleep(1000) local body = {tags = bug_data.tags} local err, body = LuaToJSON(body) local err, info = AsyncWebRequest{ method = "POST", url = g_MantisUrl .. "/api/rest/issues/" .. tostring(issueId) .. "/tags", headers = headers, body = body, timeout = 60000, } end end print("Report sent.") local url = issueId and (g_MantisUrl .. "/view.php?id=" .. issueId) return nil, url end if FirstLoad then const.BugPriorityNames = {"none", "low", "normal", "high", "urgent", "immediate" } const.BugPriorityValues = { none = 10, low = 20, normal = 30, high = 40, urgent = 50, immediate = 60, } const.BugSeverityNames = { "feature", "trivial", "text", "tweak", "minor", "major", "crash", "block" } const.BugSeverityValues = { feature = 10, trivial = 20, text = 30, tweak = 40, minor = 50, major = 60, crash = 70, block = 80, } const.BugReproducibilityNames = { "always", "sometimes", "random", "unable to reproduce", "have not tried", "N/A" } const.BugReproducibilityValues = { always = 10, sometimes = 30, random = 50, ["have not tried"] = 70, ["unable to reproduce"] = 90, ["N/A"] = 100, } end BugReportPlatformTagsToName = { -- [BugReportTag] = name (according to Platform.name) ["XB1"] = "xbox_one", ["XSX"] = "xbox_series", ["PS4"] = "ps4", ["PS5"] = "ps5", } local tempdir = "TmpData/BugReport" DefineClass.XBugReportDlg = { __parents = { "XDialog" }, properties = { { category = "General", id = "FocusSummaryOnOpen", editor = "bool", default = true, }, }, IdNode = true, Background = RGB(240, 240, 240), tempdir = "", game_specific_info = "", scribble = false, last_report_error_hash = false, file_attachments = false, init_summary = false, init_descr = false, report_params = false, } function XBugReportDlg:Open(...) XDialog.Open(self, ...) self:SetZOrder(BaseLoadingScreen.ZOrder + 1) self:InitControls() self:ScreenshotDrawOn() end if Platform.ged and Platform.developer then function OnMsg.CreateXBugReportDlg() if next(HGMembers) then return end local default_path = HGMember:GetSavePath() if io.exists(default_path) then pdofile(default_path) end end end function GatherBugReportMetadata(filename) local metadata = GatherGameMetadata() metadata.displayname = filename metadata.savename = filename .. ".savegame.sav" metadata.timestamp = os.time() metadata.playtime = GetCurrentPlaytime() metadata.isDev = true return metadata end if FirstLoad then g_LastAutosaveFilename = false end function OnMsg.SaveGameDone(name, autosave, err) if autosave and not err then g_LastAutosaveFilename = name or false end end function OnMsg.ChangeMap() g_LastAutosaveFilename = false end function OnMsg.LoadGame() g_LastAutosaveFilename = false end function XBugReportDlg:GetSendButtonControl() local children = GetChildrenOfKind(self, "XTextButton") for _, child in ipairs(children) do if child.Id == "idOK" then return child end end end function GedGetMantisToken() return AccountStorage and AccountStorage.MantisToken end function GedSaveMantisToken(socket, token) AccountStorage.MantisToken = token SaveAccountStorage(5000) end function XBugReportDlg:Report() local container = self.idScrollArea local reporter = container.idReporter and container.idReporter:GetValue() local handler = container.idAssignTo and container.idAssignTo:GetValue() local tags_presets = Platform.ged and g_GedApp.bug_report_tags or PresetArray("BugReportTag") local tags local params = self.report_params or empty_table local sendButton = self:GetSendButtonControl() sendButton:SetEnabled(false) for _, tag in ipairs(tags_presets) do local toggle_btn = container["id" .. tag.id] if toggle_btn and toggle_btn:GetToggled() or (Platform.goldmaster and tag.Automatic) then tags = tags or {} table.insert(tags, {name = tag.id}) end end for _, tag in ipairs(params.tags) do if not table.find(tags, "name", tag) then tags = tags or {} table.insert(tags, {name = tag}) end end local include_extra_info = not params.no_extra_info and (config.ForceIncludeExtraInfo or container.idExtraInfo and container.idExtraInfo:GetCheck()) local include_screenshot = not params.no_screenshot and (config.ForceIncludeScreenshot or container.idScreenshotCheck and container.idScreenshotCheck:GetCheck()) local category = params.category or container.idCategory and container.idCategory:GetValue() local priority = params.priority or container.idPriority and container.idPriority:GetValue() local severity = params.severity or container.idSeverity and container.idSeverity:GetValue() local reproducibility = container.idReproducibility and container.idReproducibility:GetValue() local target_version = container.idTargetVersion and container.idTargetVersion:GetValue() if params.mod_related then tags = tags or {} table.insert_unique(tags, {name = "Mods"}) end local summary = container.idSummary:GetText() if Platform.ged then summary = "[Ged] " .. summary end if Platform.linux then summary = "[Linux] " .. summary end if Platform.osx then summary = "[OSX] " .. summary end if Platform.xbox then summary = "[Xbox] " .. summary end if Platform.playstation then summary = "[Playstation] " .. summary end CreateRealTimeThread(function() local remember_data = LocalStorage.dlgBugReport remember_data.reporter = reporter remember_data.category = category remember_data.handler = handler remember_data.target_version = target_version SaveLocalStorage() local description = container.idDescription:GetText() or "" if params.append_description then description = description .. "\n" .. params.append_description end local reporter_token, reporter_name if container.idAPIToken and container.idAPIToken:GetVisible() then reporter_token = container.idAPIToken:GetText() end if (reporter_token or "") == "" then reporter_token, reporter_name = GetReporter(reporter) elseif AccountStorage then AccountStorage.MantisToken = reporter_token SaveAccountStorage(5000) elseif Platform.ged and rawget(_G, "g_GedApp") then g_GedApp:Send("GedSaveMantisToken", reporter_token) end local extra_info local note = "" if include_extra_info then note = {} if not reporter_token and Libs.Network then note[#note + 1] = print_format("AccountId:", netAccountId) end if insideHG() then note[#note + 1] = print_format("Host:", sockGetHostName(), ", Ip: (", table.concat({ LocalIPs() }, ", "), ")\n") end if (BuildVersion or "") ~= "" then note[#note + 1] = print_format("Build version:", BuildVersion) end if (BuildBranch or "") ~= "" then note[#note + 1] = print_format("Build branch:", BuildBranch) end note[#note + 1] = print_format("Revision Lua/OrgLua/Assets: ", LuaRevision, OrgLuaRevision, AssetsRevision) note[#note + 1] = print_format("Platform/provider/variant:", Platform.developer and "Developer" or "", PlatformName(), ProviderName(), VariantName()) note[#note + 1] = print_format("GPU:", config.GraphicsApi, GetGpuDescription(), GetGpuDriverVersion()) note[#note + 1] = print_format("Language:", GetLanguage()) note = table.concat(note, "\n") extra_info = self.game_specific_info end if not reporter_token then reporter_token = AutoReporter end local dest_filename local ctrlScreenshot = container.idScreenshot local screenshot_image = include_screenshot and ctrlScreenshot and ctrlScreenshot:GetImage() or "" if screenshot_image ~= "" then local dest_dir, dest_name = SplitPath(screenshot_image) local width, height = ctrlScreenshot:Getcontent_box_size():xy() if height > 0 then local game_width, game_height = UIL.GetScreenSize():xy() local width_multiplier = 1000 * game_width / width local height_multiplier = 1000 * game_height / height local pts = {} if self.scribble then for _, scribble in ipairs(self.scribble) do for i=1,#scribble-1 do local x = MulDivRound(scribble[i]:x(), width_multiplier, 1000) local y = MulDivRound(scribble[i]:y(), height_multiplier, 1000) pts[#pts+1] = point(x, y) x = MulDivRound(scribble[i+1]:x(), width_multiplier, 1000) y = MulDivRound(scribble[i+1]:y(), height_multiplier, 1000) pts[#pts+1] = point(x, y) end end end dest_filename = dest_dir .. dest_name .. "-scribbles.jpg" OverlayLines(screenshot_image, dest_filename, pts) end end -- Could be either an issue id or an issue URI local appendToBug = container.idAppendToExistingBug and container.idAppendToExistingBug:GetText() if appendToBug and appendToBug == "" then appendToBug = nil end if appendToBug and string.starts_with(appendToBug, "http") then -- Extract the issue id from the URI local bug_id = string.match(appendToBug, "id=([0-9]+)") assert(bug_id, "Append to existing issue - Invalid issue URI") if bug_id then appendToBug = bug_id else appendToBug = nil end end l_bug_report_counter = l_bug_report_counter + 1 DebugPrint(string.format("\n\nBUG REPORT %d\n", l_bug_report_counter)) DebugPrint("-----------------------------------------------------------------------------------\n") if appendToBug then DebugPrint(string.format("Appended to existing issue: %s\n", appendToBug)) end DebugPrint(string.format("Summary: %s\n", summary)) DebugPrint(string.format("Description: %s\n", description)) DebugPrint("\n\n") if Platform.developer and Platform.pc then DebugPrint(string.format("System memory load: %d %%\n", GetMemoryInfo().used)) DebugPrint(string.format("Process memory usage: %d MB\n", CurProcessMemUsage(1024 * 1024))) DebugPrint(string.format("Process avg CPU load: %d %%\n", CurProcessCpuUsage())) end local member_preset = HGMembers and HGMembers[handler] or empty_table local is_level_designer = member_preset.group == "Level Design" handler = member_preset.mantis_id priority = const.BugPriorityValues and const.BugPriorityValues[priority] or nil severity = const.BugSeverityValues and const.BugSeverityValues[severity] or nil reproducibility = const.BugReproducibilityValues and const.BugReproducibilityValues[reproducibility] or nil local bug_autosave_filename local bug_save_filename local bug_save_pstr local attach_save local attach_autosave local can_save = CanSaveGame() == "persist" if GetMap() ~= "" and (extra_info or params.force_save_check) and rawget(_G, "SaveGameBugReport") then attach_autosave = container.idLastAutosave and container.idLastAutosave:GetCheck() if attach_autosave and g_LastAutosaveFilename then if Platform.console then bug_autosave_filename = string.format("%s/%s", self.tempdir, g_LastAutosaveFilename) local err = Savegame.Export(g_LastAutosaveFilename, bug_autosave_filename) if err then bug_autosave_filename = nil end elseif Platform.pc then bug_autosave_filename = GetPCSaveFolder() .. g_LastAutosaveFilename else assert(not "Autosave attaching not supported on current platform!") end end attach_save = can_save and container.idSaveGame:GetCheck() if attach_save then local display_name = os.date("Bug %Y-%m-%d %H.%M.%S") local savepath --[[ local save_terrain_delta = config.SaveTerrainGridDelta config.SaveTerrainGridDelta = false -- turning off the delta grid save --]] local msg = CreateMessageBox(self, T(273706464856, "Bug report"), T(646010194429, "Saving, please wait..."), T(325411474155, "OK")) msg:PreventClose() Sleep(100) local err, savegame_pstr = SaveGameBugReportPStr(display_name) if err then print("Bug report savegame error: ", err) end bug_save_filename = display_name .. string.format(" %s ", LuaRevision) .. string.gsub(summary, '[/?<>\\:*|"]', "_") .. ".savegame.sav" bug_save_pstr = savegame_pstr msg:Close() --config.SaveTerrainGridDelta = save_terrain_delta -- returning initial set up for delta grid save end end --try to attach items.lua and items, metadata, scripts lua of the mod local attachModTagAndFiles = container.idModRelated and container.idModRelated:GetCheck() local attachMod = container.idAttachMod and container.idAttachMod:GetCheck() local mod = params.mod if not mod then if Platform.ged then mod = g_GedApp and g_GedApp:Call("GedGetLastEditedMod") else mod = LastEditedMod end end if attachModTagAndFiles or (attachMod and mod and Platform.developer) then if mod then local err, script_files = AsyncListFiles(mod.mod_os_path .. "Code", "*.lua") if not err then mod.mod_scripts_path = script_files end mod.attachModTagAndFiles = true end end if attachMod and mod then mod.attachMod = true end local msg = CreateMessageBox(self, T(273706464856, "Bug report"), T(968163992960, "Sending bug report, please wait..."), T(325411474155, "OK")) msg:PreventClose() local orig_log = extra_info and GetLogFile() local bug_data = { reporter = reporter_token, tags = tags, image = dest_filename, log = orig_log, autosave = bug_autosave_filename, save = bug_save_filename, save_pstr = bug_save_pstr, mod = mod, handler = handler, project = Platform.ged and g_GedApp.mantis_project_id or const.MantisProjectID or 9, -- MantisProjectID for "General" copy_url_btn = Platform.ged and g_GedApp.mantis_copy_url_btn or (not Platform.ged and const.MantisCopyUrlButton), category = category, appendToBug = appendToBug, summary = is_level_designer and (string.format("(%s) %s", GetMap(), summary)) or summary, description = description, extra_info = extra_info, note = note, -- any note info would be added as a bug note in the report priority = priority, severity = severity, reproducibility = reproducibility, target_version = target_version, files = self.file_attachments, } DebugPrint("-----------------------------------------------------------------------------------\n") FlushLogFile() -- rename the log so that it's easier to open in the browser local new_log if orig_log and orig_log ~= "" then local dir, name, ext = SplitPath(orig_log) new_log = string.format("%s/%s.txt", self.tempdir, name) end if new_log and not AsyncCopyFile(orig_log, new_log) and extra_info then bug_data.log = new_log end local failed, url = ReportBug(bug_data) msg:Close() Msg("BugReportResult", failed) if failed then WaitMessage(self, T(273706464856, "Bug report"), T{619000999242, "Sending bug report failed:\n.", http_error_code = failed}, T(325411474155, "OK"), self) else -- keep preferences for the saves in local storage if attach_save or attach_autosave then LocalStorage.BugAttachAutosave = attach_autosave LocalStorage.BugAttachSave = attach_save SaveLocalStorageDelayed() end local copy_url_button = not Platform.console and url if const.MantisCopyUrlButton ~= nil or Platform.ged then copy_url_button = copy_url_button and bug_data.copy_url_btn end if copy_url_button then local resp = WaitQuestion(self, T(273706464856, "Bug report"), T(128433133976, "Bug report sent successfully."), T(325411474155, "OK"), T(632653390877, "Copy URL"), self) if resp == "cancel" then CopyToClipboard(url) end else WaitMessage(self, T(273706464856, "Bug report"), T(128433133976, "Bug report sent successfully."), T(325411474155, "OK"), self) end end if not failed then self:Close() end sendButton:SetEnabled(true) end) end function XBugReportDlg:ScreenshotDrawOn() local buttonDown = false local last_pt = false local container = self.idScrollArea container.idScreenshot.OnMouseButtonDown = function(_, pt, button) container.idScreenshotText:SetVisible(false) if button == "R" then self.scribble = false self:Invalidate() return "break" elseif button == "L" then buttonDown = true pt = pt - container.idScreenshot.box:min() if not self.scribble then self.scribble = { { pt } } else self.scribble[1 + #self.scribble] = { pt } end last_pt = pt self.desktop:SetMouseCapture(container.idScreenshot) end return "break" end container.idScreenshot.OnMouseButtonUp = function(_, pt, button) if button == "L" then buttonDown = false self.desktop:SetMouseCapture() return "break" end end container.idScreenshot.OnMousePos = function(_, pt) if buttonDown and self.scribble then local bbox = container.idScreenshot.box pt = ClampPoint(pt, bbox) pt = pt - bbox:min() if pt:Dist2D(last_pt) > 5 then local last_scribble = self.scribble[#self.scribble] last_scribble[1 + #last_scribble] = pt last_pt = pt self:Invalidate() end end end container.idScreenshot.DrawContent = function(img) XImage.DrawContent(img) if self.scribble then local ptS0 = img.box:min() local ptS1 = img.box:min() + point(1, 1) for i = 1, #self.scribble do local polyline = self.scribble[i] if #polyline > 2 then for j=2,#polyline do UIL.DrawLine(ptS1 + polyline[j - 1], ptS1 + polyline[j], RGB(0, 0, 0) ) UIL.DrawLine(ptS0 + polyline[j - 1], ptS0 + polyline[j], RGB(255, 0, 0)) end end end end end end function XBugReportDlg:HideComboWindow(combo_id) local combo_container = self.idScrollArea:ResolveId("idComboContainer") if combo_container then local combo = combo_container:ResolveId(combo_id) if not combo then return end local window = combo:GetParent() window:SetVisible(false) window:SetDock("ignore") end end function XBugReportDlg:InitControls() local container = self.idScrollArea if Platform.developer and container.idAssignTo then container.idAssignTo.OnValueChanged = function(this, value) local member = HGMembers and HGMembers[value] if not member then return end local group = member.group if group == "Level Design" then container.idCategory:SetValue("Maps") elseif group == "Code" then container.idCategory:SetValue("Code") elseif group == "Art" then if member.Animation then container.idCategory:SetValue("Animation") else container.idCategory:SetValue("Art") end elseif group == "Design" then container.idCategory:SetValue("Design") end end end if not (Platform.developer and insideHG()) then if container.idComboContainer then self:HideComboWindow("idReporter") self:HideComboWindow("idAssignTo") self:HideComboWindow("idCategory") self:HideComboWindow("idTargetVersion") end if container.idExtraInfo then container.idExtraInfo:SetVisible(false) container.idExtraInfo:SetDock("ignore") end if container.idScreenshotCheck then container.idScreenshotCheck:SetVisible(false) container.idScreenshotCheck:SetDock("ignore") end if container.idAssignTo then container.idAssignTo:SetValue(" ") end else if container.idComboContainer then self:HideComboWindow("idSeverity") self:HideComboWindow("idReproducibility") end if container.idAPIToken then container.idAPIToken:GetParent():SetVisible(false) container.idAPIToken:GetParent():SetDock("ignore") end end local oldOnTextChanged = container.idSummary.OnTextChanged container.idSummary.OnTextChanged = function(this) local sendButton = self:GetSendButtonControl() sendButton:SetEnabled(container.idSummary:GetText() ~= "") return oldOnTextChanged(this) end local ctrlSave = container.idSaveGame if ctrlSave then ctrlSave:SetCheck(Platform.goldmaster or LocalStorage.BugAttachSave) ctrlSave:SetEnabled((not Platform.goldmaster or not Platform.asserts) and CanSaveGame()) ctrlSave:SetText(config.CustomAttachSavegameText or (ctrlSave.Translate and T(950219570922, "Attach savegame") or "Attach savegame")) end if container.idLastAutosave then container.idLastAutosave:SetCheck(Platform.goldmaster or LocalStorage.BugAttachAutosave) container.idLastAutosave:SetEnabled(not Platform.goldmaster and g_LastAutosaveFilename) end local sendButton = self:GetSendButtonControl() sendButton.OnPress = function(this) self:Report() end ForceShowMouseCursor("bug report") end function XBugReportDlg:Done() UnforceShowMouseCursor("bug report") Msg("BugReportEnd") end function XBugReportDlg:FillReport(screenshot) local container = self.idScrollArea local ctrlScreenshot = container:ResolveId("idScreenshot") local function HideCheckBox(id) local ctrl = container:ResolveId(id) if ctrl then ctrl:SetCheck(false) ctrl:SetVisible(false) end end --check params to decide what to show/hide local params = self.report_params or empty_table if params.no_extra_info then HideCheckBox("idExtraInfo") HideCheckBox("idSaveGame") HideCheckBox("idLastAutosave") HideCheckBox("idScreenshotCheck") ctrlScreenshot:SetVisible(false) elseif not params.no_screenshot and screenshot then ctrlScreenshot:SetImage(screenshot) else HideCheckBox("idScreenshotCheck") ctrlScreenshot:SetVisible(false) end if Platform.ged then HideCheckBox("idSaveGame") HideCheckBox("idLastAutosave") elseif params.no_attach_auto_save then HideCheckBox("idLastAutosave") end if params.no_platform_tags then self:HideComboWindow("idPlatformTagsLabel") end if params.no_game_tags then self:HideComboWindow("idGameTagsLabel") end if params.no_header_combos then local ctrlComboCont = container:ResolveId("idComboContainer") if ctrlComboCont then ctrlComboCont:SetVisible(false) end end if params.no_api_token then local ctrlApi = container.idAPIToken if ctrlApi then ctrlApi:GetParent():SetVisible(false) ctrlApi:GetParent():SetDock("ignore") end end local ctrlAttachMod = container.idAttachMod if ctrlAttachMod and params.mod then ctrlAttachMod:SetVisible(true) ctrlAttachMod:SetCheck(false) ctrlAttachMod:SetEnabled(true) local translatedT = T{442070512780, "Attach last edited mod ''", mod_name = Untranslated(params.mod.title)} local untranslatedT = "Attach last edited mod '" .. params.mod.title .. "'" ctrlAttachMod:SetText(not ctrlAttachMod.Translate and untranslatedT or translatedT) if params.mod_related ~= nil then params.mod_related = true end elseif ctrlAttachMod then ctrlAttachMod:SetVisible(false) end local ctrlModRelated = container.idModRelated if ctrlModRelated and params.mod_related and Platform.goldmaster then --only for goldmaster as otherwise you could just set the tag manually ctrlModRelated:SetVisible(true) ctrlModRelated:SetCheck(true) ctrlModRelated:SetEnabled(true) ctrlModRelated:SetText(not ctrlModRelated.Translate and "Modding issue" or T(378715147437, "Modding issue")) elseif ctrlModRelated then ctrlModRelated:SetVisible(false) end if not LocalStorage.dlgBugReport then LocalStorage.dlgBugReport = {} end -- mantis users local users = {} ForEachPreset("HGMember", function(preset, group, users) table.insert(users, preset.id) end, users) table.sort(users, CmpLower) -- reporter local items = {} local reporter_token, reporter_name if container.idAPIToken and container.idAPIToken:GetVisible() then if AccountStorage then reporter_token = AccountStorage.MantisToken elseif Platform.ged and rawget(_G, "g_GedApp") then reporter_token = g_GedApp:Call("GedGetMantisToken") end end if not reporter_token then reporter_token, reporter_name = GetReporter() else container.idAPIToken:SetText(reporter_token) end local reporter = reporter_token and reporter_name if reporter then table.insert(items, reporter) end for i = 1, #users do local user = users[i] if user ~= reporter then table.insert(items, user) end end if container.idReporter then container.idReporter:SetItems(items) container.idReporter:SetValue(reporter_name) end -- category if container.idCategory and const.Categories then container.idCategory:SetItems(const.Categories) container.idCategory:SetValue(params.category or LocalStorage.dlgBugReport.category or const.Categories[const.DefaultCategory]) end -- handler table.insert(items, 1, { name = " ", id = " "}) local map_author = mapdata and mapdata.Author if map_author then table.insert(items, 2, { name = map_author .. " (map author)", id = map_author}) end if container.idAssignTo then container.idAssignTo:SetItems(items) container.idAssignTo:SetValue(LocalStorage.dlgBugReport.handler or " ") end -- priority if container.idPriority then if params.no_priority then self:HideComboWindow("idPriority") end container.idPriority:SetItems(const.BugPriorityNames) container.idPriority:SetValue(params.priority or const.DefaultBugPriority or "normal") end -- severity if container.idSeverity then container.idSeverity:SetItems(const.BugSeverityNames) container.idSeverity:SetValue(params.severity or const.DefaultBugSeverity or "minor") end -- reproducibility if container.idReproducibility then container.idReproducibility:SetItems(const.BugReproducibilityNames) container.idReproducibility:SetValue(const.DefaultBugReproducibility or "have not tried") end -- target versions if container.idTargetVersion then if const.TargetVersions then local items = { {id = "", name = ""} } for i=1, #const.TargetVersions do local version = const.TargetVersions[i] items[#items + 1] = { id = version, name = version } end container.idTargetVersion:SetItems(items) if not table.find(items, "name", LocalStorage.dlgBugReport.target_version) then LocalStorage.dlgBugReport.target_version = "" end container.idTargetVersion:SetValue(LocalStorage.dlgBugReport.target_version or const.DefaultTargetVersion) else container.idTargetVersion:SetValue(const.DefaultTargetVersion) container.idTargetVersion:SetVisible(false) end end container.idSummary:SetText(self.init_summary or "") container.idDescription:SetText(self.init_descr or "") local sendButton = self:GetSendButtonControl() sendButton:SetEnabled(self.init_summary and self.init_descr) if params.summary_readonly and self.init_summary then container.idSummary:SetEnabled(false) container.idSummary.OnTextChanged = nil sendButton:SetEnabled(true) end if Platform.asserts and not self.init_summary then local function TryFillError(assert_format, error_msg, error_time) local valid if error_time and error_msg and error_msg ~= "" then local elapsed = RealTime() - error_time valid = elapsed >= 0 and elapsed < 10 * 1000 end if not valid then return end local hash = xxhash(error_msg, error_time) if hash == self.last_report_error_hash then return end self.last_report_error_hash = hash local info = error_msg local idx = string.find(info, "\n\n", 1, true) if idx then info = string.sub(info, 1, idx - 1) end local idx1, idx2 = string.find(info, ":%d+: ") if idx2 then info = string.sub(info, idx2) else idx1, idx2 = string.find(info, "%(%d+%): ") if idx2 then info = string.sub(info, idx2) end end if #info > 40 then local i1 = string.find(info, "\n", 20, true) local i2 = string.find(info, ". ", 20, true) local i = i1 and i2 and Min(i1, i2) or i1 or i2 if i then info = string.sub(info, 1, i - 1) else info = string.sub(info, 1, 60) .. "..." end end self.report_params = self.report_params or {} self.report_params["tags"] = table.create_add(self.report_params["tags"], "Assert") container.idSummary:SetText(string.format(assert_format, info)) container.idDescription:SetText(error_msg) return true end local assert_format = "[Assert] %s" local error_msg, error_time = GetLastError() if Platform.ged then -- fetch the last assert in the game local error_msg2, error_time2 = g_GedApp:GetGameError() if error_msg2 and (not error_msg or error_time2 > error_time) then error_msg, error_time = error_msg2, error_time2 assert_format = "[Game Assert] %s" end assert_format = "[Ged] " .. assert_format end if not TryFillError(assert_format, error_msg, error_time) then local MarkedErrors = rawget(_G, "MarkedErrors") local vme = MarkedErrors and MarkedErrors[#MarkedErrors] if vme and vme.report_time then error_msg = vme.msg .. "\n\nSource: " .. ValueToStr(vme.source) error_time = vme.report_time assert_format = "[VME] %s" TryFillError(assert_format, error_msg, error_time) end end end end function XBugReportDlg:ShowReport(screenshot) local container = self.idScrollArea self:FillReport(screenshot) self:SetModal() if self.FocusSummaryOnOpen then container.idSummary:SetFocus() container.idSummary:SelectAll() end end function XBugReportDlg:OnShortcut(shortcut, source, ...) local container = self.idScrollArea if shortcut == "ButtonB" or shortcut == "Escape" then self:Close() return "break" elseif shortcut == "Enter" or shortcut == "Ctrl-Enter" then local sendButton = self:GetSendButtonControl() if sendButton:GetEnabled() then self:Report() end return "break" end return XDialog.OnShortcut(self, shortcut, source, ...) end ------------- Creation ------------ local function CreateScreenshot(screendir) local filename local i = 1 while true do local name = string.format("%s/BugReport%04d.png", screendir, i) if not io.exists(name) then filename = name break end i = i + 1 end local oldInterfaceInScreenshot = hr.InterfaceInScreenshot hr.InterfaceInScreenshot = 1 local error = "init" local size = UIL.GetScreenSize() if WriteScreenshot(filename, size:x(), size:y(), box(point20, size), 75) then while true do local finished, err = ScreenshotWritten() if finished or err then error = err break end _G.Sleep(100) end end hr.InterfaceInScreenshot = oldInterfaceInScreenshot if error then print("Failed to create screenshot \"%s\": %s", filename, error) end return filename end if FirstLoad then l_bug_report_counter = 0 end function CreateXBugReportDlg(summary, descr, files, params) local template_id = config.BugReporterXTemplateID local dlg = GetDialog(template_id) if dlg then return end local lua_load_pct = GetLuaLoadPct() Msg("CreateXBugReportDlg") WaitNextFrame(2) local success, err = io.createpath(tempdir) if not success then print("Failed to create a temp folder for bug report:", err) tempdir = "" end local no_screenshot = params and params.no_screenshot local screenshot = (not no_screenshot) and CreateScreenshot(tempdir) dlg = OpenDialog(template_id) dlg.init_summary = summary dlg.init_descr = descr dlg.file_attachments = files or false dlg.report_params = params local prints = {} local function bug_print(...) prints[#prints + 1] = print_format(...) end for k, v in sorted_pairs(GamepadUIStyle) do bug_print("GamepadUIStyle:", k, v) end if not config.DisableOptions then bug_print("Local Options:", TableToLuaCode(GetFullEngineOptions(), " ")) if AccountStorage then bug_print("Account Options:", TableToLuaCode(GetFullAccountOptions(), " ")) end bug_print(string.format("\nOptions: (paste in the console)\n%s", GetOptionsString())) bug_print("") end if lua_load_pct > 0 then bug_print("Lua Load:", lua_load_pct, "%") end Msg("BugReportStart", bug_print, dlg) dlg.tempdir = tempdir dlg.game_specific_info = table.concat(prints, "\n") dlg:ShowReport(screenshot) return dlg end function WaitXBugReportDlg(summary, descr, files, params) local dlg while true do dlg = dlg or CreateXBugReportDlg(summary, descr, files, params) if WaitMsg("BugReportEnd", 10000) and dlg then break end end end function CloseXBugReportDlg() CloseDialog(config.BugReporterXTemplateID) end