102 Patches: Detours to the Rescue
C reference for DttR maintainers and modders.
Loading...
Searching...
No Matches
imgui_error_ui.c
Go to the documentation of this file.
1#include <dttr_imgui.h>
2#include <dttr_path.h>
3
4#include <sds.h>
5#include <string.h>
6
7#define DTTR_ERROR_UI_WINDOW_W 560
8#define DTTR_ERROR_UI_INITIAL_WINDOW_H 120
9#define DTTR_ERROR_UI_BUTTON_H 28.0f
10#define DTTR_ERROR_UI_TEXT_PADDING_X 18.0f
11#define DTTR_ERROR_UI_TEXT_PADDING_Y 16.0f
12#define DTTR_ERROR_UI_STACK_BOX_H 150.0f
13
14#ifndef DTTR_VERSION
15#define DTTR_VERSION "unknown"
16#endif
17
18static const char *const ERROR_TITLE = "DttR: Error";
19static const char *const HEADER_TITLE = "102 Crashes: Traces to the Rescue!";
20static const char *const CRASH_DETAILS_MARKER = "\n\nContextFlags=";
21static const char *const STACK_TRACE_MARKER = "\n\nStack trace:";
22static const char *const DUMP_MARKER = "\n\nDump written to:";
23static const char *const REPORT_MARKER = "\n\nFeel free to report this error";
24
25typedef struct {
26 const char *text;
27 const char *end;
28} text_span;
29
30typedef struct {
31 const char *summary_end;
32 const char *report_text;
35
36static text_span span(const char *text, const char *end) {
37 return (text_span){text, end};
38}
39
40static size_t span_len(text_span span) {
41 return (size_t)(span.end - span.text);
42}
43
45 return sdsnewlen(span.text, span_len(span));
46}
47
51
53 igSetCursorPosX(text_padding_x(ctx));
54}
55
56static error_message parse_error_message(const char *message) {
57 const char *stack = strstr(message, STACK_TRACE_MARKER);
58 if (!stack) {
59 return (error_message){0};
60 }
61
62 const char *details = stack;
63 const char *crash_details = strstr(message, CRASH_DETAILS_MARKER);
64 if (crash_details && crash_details < stack) {
65 details = crash_details;
66 }
67
68 const char *details_text = details + 2;
69 const char *report_text = strstr(details_text, REPORT_MARKER);
70
71 return (error_message){
72 .summary_end = details,
73 .report_text = report_text,
74 .stack_trace = sdsnewlen(
75 details_text,
76 report_text ? (size_t)(report_text - details_text) : strlen(details_text)
77 ),
78 };
79}
80
83 igPushTextWrapPos(igGetWindowWidth() - text_padding_x(ctx));
84 igTextWrapped("%.*s", (int)span_len(text), text.text);
85 igPopTextWrapPos();
86}
87
88static void draw_wrapped_text(const DTTR_ImGuiDialogContext *ctx, const char *text) {
89 draw_wrapped_text_span(ctx, span(text, text + strlen(text)));
90}
91
94 const char *label,
95 const char *url
96) {
98 igPushStyleColor_Vec4(ImGuiCol_TextLink, DTTR_IMGUI_COLOR_LINK);
99 igTextLinkOpenURL(label, url);
100 igPopStyleColor(1);
101}
102
103static sds file_url_for_parent_dir(const char *path) {
104 const char *last_separator = NULL;
105 for (const char *p = path; *p; p++) {
106 if (DTTR_Path_IsSeparator(*p)) {
107 last_separator = p;
108 }
109 }
110
111 if (!last_separator) {
112 return NULL;
113 }
114
115 sds url = sdsnew("file:///");
116 for (const char *p = path; url && p < last_separator; p++) {
117 if (*p == '\\') {
118 url = sdscatlen(url, "/", 1);
119 } else if (*p == ' ') {
120 url = sdscat(url, "%20");
121 } else {
122 url = sdscatlen(url, p, 1);
123 }
124 }
125
126 return url;
127}
128
129static void draw_dump_text(const DTTR_ImGuiDialogContext *ctx, text_span dump_text) {
130 const char *newline = memchr(dump_text.text, '\n', span_len(dump_text));
131 if (!newline || newline + 1 >= dump_text.end) {
132 draw_wrapped_text_span(ctx, dump_text);
133 return;
134 }
135
136 sds label = sdsnewspan(span(newline + 1, dump_text.end));
137 if (label) {
138 sdstrim(label, "\r\n");
139 }
140
141 sds url = label ? file_url_for_parent_dir(label) : NULL;
142 if (!label || sdslen(label) == 0 || !url) {
143 sdsfree(label);
144 sdsfree(url);
145 draw_wrapped_text_span(ctx, dump_text);
146 return;
147 }
148
149 draw_wrapped_text_span(ctx, span(dump_text.text, newline));
150 draw_clickable_text(ctx, label, url);
151 sdsfree(label);
152 sdsfree(url);
153}
154
155static void draw_report_text(const DTTR_ImGuiDialogContext *ctx, const char *report_text) {
156 const char *text = report_text + 2;
157 const char *url = strchr(text, '\n');
158 if (!url || !url[1]) {
159 draw_wrapped_text(ctx, text);
160 return;
161 }
162
163 draw_wrapped_text_span(ctx, span(text, url));
164 text_span url_text = span(url + 1, url + 1 + strcspn(url + 1, "\r\n"));
165 sds url_copy = sdsnewspan(url_text);
166 if (!url_copy || sdslen(url_copy) == 0) {
167 sdsfree(url_copy);
168 draw_wrapped_text(ctx, text);
169 return;
170 }
171
172 draw_clickable_text(ctx, url_copy, url_copy);
173 sdsfree(url_copy);
174}
175
176static const char *find_summary_dump(const error_message *message, const char *summary) {
177 const char *dump_text = strstr(summary, DUMP_MARKER);
178 return dump_text && dump_text < message->summary_end ? dump_text : NULL;
179}
180
183 const char *summary,
184 const error_message *message
185) {
186 const char *dump_text = find_summary_dump(message, summary);
187
189
190 if (message->report_text) {
193 }
194
196 const float stack_width = igGetWindowWidth() - (text_padding_x(ctx) * 2.0f);
197 igPushStyleColor_Vec4(ImGuiCol_FrameBg, DTTR_IMGUI_COLOR_STACK_FRAME_BG);
198 igPushStyleColor_Vec4(ImGuiCol_FrameBgHovered, DTTR_IMGUI_COLOR_STACK_FRAME_BG);
199 igPushStyleColor_Vec4(ImGuiCol_FrameBgActive, DTTR_IMGUI_COLOR_STACK_FRAME_BG);
200 igInputTextMultiline(
201 "##stack_trace",
202 message->stack_trace,
203 sdslen(message->stack_trace) + 1,
204 (
205 ImVec2_c
206 ){stack_width, DTTR_ImGuiDialog_ScaledFloat(ctx, DTTR_ERROR_UI_STACK_BOX_H)},
207 ImGuiInputTextFlags_ReadOnly,
208 NULL,
209 NULL
210 );
211 igPopStyleColor(3);
212
213 if (dump_text) {
215 draw_dump_text(ctx, span(dump_text + 2, message->summary_end));
216 }
217
219}
220
221bool DTTR_ImGui_ErrorShow(const char *title, const char *message) {
222 const char *window_title = title ? title : ERROR_TITLE;
223 const char *safe_message = message ? message : "";
224 error_message parsed_message = parse_error_message(safe_message);
225
228 &ctx,
232 )) {
233 sdsfree(parsed_message.stack_trace);
234 return false;
235 }
236
237 bool running = true;
238 while (running) {
242
243 if (DTTR_ImGuiDialog_BeginRoot(&ctx, window_title, ImGuiWindowFlags_None)) {
244 const ImVec2_c ok_button_size = {
247 };
248
250 if (parsed_message.stack_trace) {
251 draw_copyable_stack_trace(&ctx, safe_message, &parsed_message);
252 } else {
254 &ctx,
255 safe_message,
258 );
259 }
260
261 DTTR_ImGuiDialog_CenterNextItem(ok_button_size.x);
262 if (DTTR_ImGuiDialog_Button(&ctx, "##ok", "OK", ok_button_size)) {
263 running = false;
264 }
265
266 igDummy((ImVec2_c){
267 0.0f,
269 });
271 }
272
275 }
276
278 sdsfree(parsed_message.stack_trace);
279 return true;
280}
void void * ctx
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
void DTTR_ImGuiDialog_End(DTTR_ImGuiDialogContext *ctx)
void DTTR_ImGuiDialog_DrawHeader(const DTTR_ImGuiDialogContext *ctx, const char *title, const char *version)
void DTTR_ImGuiDialog_CenterNextItem(float item_width)
void DTTR_ImGuiDialog_Render(DTTR_ImGuiDialogContext *ctx)
bool DTTR_ImGuiDialog_RefreshScale(DTTR_ImGuiDialogContext *ctx)
bool DTTR_ImGuiDialog_BeginRoot(DTTR_ImGuiDialogContext *ctx, const char *title, ImGuiWindowFlags flags)
float DTTR_ImGuiDialog_ScaledFloat(const DTTR_ImGuiDialogContext *ctx, float value)
void DTTR_ImGuiDialog_OffsetCursorY(const DTTR_ImGuiDialogContext *ctx, float amount)
void DTTR_ImGuiDialog_DrawPaddedText(const DTTR_ImGuiDialogContext *ctx, const char *message, float padding_x, float padding_y)
void DTTR_ImGuiDialog_ProcessEvents(const DTTR_ImGuiDialogContext *ctx, bool *running)
bool DTTR_ImGuiDialog_Button(const DTTR_ImGuiDialogContext *ctx, const char *id, const char *label, ImVec2_c size)
void DTTR_ImGuiDialog_EndRoot()
void DTTR_ImGuiDialog_NewFrame(const DTTR_ImGuiDialogContext *ctx)
void DTTR_ImGuiDialog_FitWindowToContent(DTTR_ImGuiDialogContext *ctx, int width, float padding_y)
#define DTTR_IMGUI_COLOR_STACK_FRAME_BG
Definition dttr_imgui.h:11
bool DTTR_ImGuiDialog_Begin(DTTR_ImGuiDialogContext *ctx, const char *title, int width, int height)
#define DTTR_IMGUI_COLOR_LINK
Definition dttr_imgui.h:10
bool DTTR_Path_IsSeparator(char ch)
Definition path.c:76
static sds window_title
Definition graphics.c:34
static const char * find_summary_dump(const error_message *message, const char *summary)
static void set_text_padding_x(const DTTR_ImGuiDialogContext *ctx)
#define DTTR_ERROR_UI_TEXT_PADDING_X
static size_t span_len(text_span span)
static void draw_clickable_text(const DTTR_ImGuiDialogContext *ctx, const char *label, const char *url)
static const char *const CRASH_DETAILS_MARKER
static error_message parse_error_message(const char *message)
static void draw_copyable_stack_trace(const DTTR_ImGuiDialogContext *ctx, const char *summary, const error_message *message)
static float text_padding_x(const DTTR_ImGuiDialogContext *ctx)
bool DTTR_ImGui_ErrorShow(const char *title, const char *message)
static void draw_report_text(const DTTR_ImGuiDialogContext *ctx, const char *report_text)
static void draw_wrapped_text_span(const DTTR_ImGuiDialogContext *ctx, text_span text)
#define DTTR_ERROR_UI_WINDOW_W
static void draw_wrapped_text(const DTTR_ImGuiDialogContext *ctx, const char *text)
static sds file_url_for_parent_dir(const char *path)
static void draw_dump_text(const DTTR_ImGuiDialogContext *ctx, text_span dump_text)
#define DTTR_ERROR_UI_BUTTON_H
#define DTTR_ERROR_UI_TEXT_PADDING_Y
#define DTTR_VERSION
static const char *const REPORT_MARKER
#define DTTR_ERROR_UI_INITIAL_WINDOW_H
static sds sdsnewspan(text_span span)
static text_span span(const char *text, const char *end)
static const char *const HEADER_TITLE
static const char *const ERROR_TITLE
static const char *const DUMP_MARKER
static const char *const STACK_TRACE_MARKER
const char * report_text
const char * summary_end
const char * end
const char * text