1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
|
#include "Chat.h"
#include "Commands.h"
#include "String.h"
#include "Stream.h"
#include "Event.h"
#include "Game.h"
#include "Logger.h"
#include "Server.h"
#include "Funcs.h"
#include "Utils.h"
#include "Options.h"
#include "Drawer2D.h"
static char status[5][STRING_SIZE];
static char bottom[3][STRING_SIZE];
static char client[2][STRING_SIZE];
static char announcement[STRING_SIZE];
static char bigAnnouncement[STRING_SIZE];
static char smallAnnouncement[STRING_SIZE];
cc_string Chat_Status[5] = { String_FromArray(status[0]), String_FromArray(status[1]), String_FromArray(status[2]),
String_FromArray(status[3]), String_FromArray(status[4]) };
cc_string Chat_BottomRight[3] = { String_FromArray(bottom[0]), String_FromArray(bottom[1]), String_FromArray(bottom[2]) };
cc_string Chat_ClientStatus[2] = { String_FromArray(client[0]), String_FromArray(client[1]) };
cc_string Chat_Announcement = String_FromArray(announcement);
cc_string Chat_BigAnnouncement = String_FromArray(bigAnnouncement);
cc_string Chat_SmallAnnouncement = String_FromArray(smallAnnouncement);
double Chat_AnnouncementReceived;
double Chat_BigAnnouncementReceived;
double Chat_SmallAnnouncementReceived;
struct StringsBuffer Chat_Log, Chat_InputLog;
cc_bool Chat_Logging;
/*########################################################################################################################*
*-------------------------------------------------------Chat logging------------------------------------------------------*
*#########################################################################################################################*/
double Chat_RecentLogTimes[CHATLOG_TIME_MASK + 1];
static void ClearChatLogs(void) {
Mem_Set(Chat_RecentLogTimes, 0, sizeof(Chat_RecentLogTimes));
StringsBuffer_Clear(&Chat_Log);
}
static char logNameBuffer[STRING_SIZE];
static cc_string logName = String_FromArray(logNameBuffer);
static char logPathBuffer[FILENAME_SIZE];
static cc_string logPath = String_FromArray(logPathBuffer);
static struct Stream logStream;
static int lastLogDay, lastLogMonth, lastLogYear;
/* Resets log name to empty and resets last log date */
static void ResetLogFile(void) {
logName.length = 0;
lastLogYear = -123;
}
/* Closes handle to the chat log file */
static void CloseLogFile(void) {
cc_result res;
if (!logStream.meta.file) return;
res = logStream.Close(&logStream);
if (res) { Logger_SysWarn2(res, "closing", &logPath); }
}
/* Whether the given character is an allowed in a log filename */
static cc_bool AllowedLogNameChar(char c) {
return
c == '{' || c == '}' || c == '[' || c == ']' || c == '(' || c == ')' ||
(c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
void Chat_SetLogName(const cc_string* name) {
char c;
int i;
if (logName.length) return;
for (i = 0; i < name->length; i++) {
c = name->buffer[i];
if (AllowedLogNameChar(c)) {
String_Append(&logName, c);
} else if (c == '&') {
i++; /* skip over following color code */
}
}
}
void Chat_DisableLogging(void) {
Chat_Logging = false;
lastLogYear = -321;
Chat_AddRaw("&cDisabling chat logging");
CloseLogFile();
}
static cc_bool CreateLogsDirectory(void) {
static const cc_string dir = String_FromConst("logs");
cc_result res;
/* Utils_EnsureDirectory cannot be used here because it causes a stack overflow */
/* when running the game and an error occurs when trying to create the directory */
/* This happens because when running the game, Logger_WarnFunc is changed to log */
/* a message in chat instead of showing a dialog box, which causes the following */
/* functions to be called in a recursive loop: */
/* */
/* Utils_EnsureDirectory --> Logger_SysWarn2 --> Chat_Add --> AppendChatLog -> OpenChatLog */
/* --> Utils_EnsureDirectory --> Logger_SysWarn2 --> Chat_Add --> AppendChatLog -> OpenChatLog */
/* --> Utils_EnsureDirectory --> Logger_SysWarn2 --> Chat_Add --> AppendChatLog -> OpenChatLog */
/* --> Utils_EnsureDirectory --> Logger_SysWarn2 --> Chat_Add --> AppendChatLog ... */
/* and so on, until eventually the stack overflows */
res = Directory_Create(&dir);
if (!res || res == ReturnCode_DirectoryExists) return true;
Chat_DisableLogging();
Logger_SysWarn2(res, "creating directory", &dir);
return false;
}
static void OpenChatLog(struct DateTime* now) {
cc_result res;
int i;
if (Platform_ReadonlyFilesystem || !CreateLogsDirectory()) return;
/* Ensure multiple instances do not end up overwriting each other's log entries. */
for (i = 0; i < 20; i++) {
logPath.length = 0;
String_Format3(&logPath, "logs/%p4-%p2-%p2 ", &now->year, &now->month, &now->day);
if (i > 0) {
String_Format2(&logPath, "%s _%i.txt", &logName, &i);
} else {
String_Format1(&logPath, "%s.txt", &logName);
}
res = Stream_AppendFile(&logStream, &logPath);
if (res && res != ReturnCode_FileShareViolation) {
Chat_DisableLogging();
Logger_SysWarn2(res, "appending to", &logPath);
return;
}
if (res == ReturnCode_FileShareViolation) continue;
return;
}
Chat_DisableLogging();
Chat_Add1("&cFailed to open a chat log file after %i tries, giving up", &i);
}
static void AppendChatLog(const cc_string* text) {
cc_string str; char strBuffer[DRAWER2D_MAX_TEXT_LENGTH];
struct DateTime now;
cc_result res;
if (!logName.length || !Chat_Logging) return;
DateTime_CurrentLocal(&now);
if (now.day != lastLogDay || now.month != lastLogMonth || now.year != lastLogYear) {
CloseLogFile();
OpenChatLog(&now);
}
lastLogDay = now.day; lastLogMonth = now.month; lastLogYear = now.year;
if (!logStream.meta.file) return;
/* [HH:mm:ss] text */
String_InitArray(str, strBuffer);
String_Format3(&str, "[%p2:%p2:%p2] ", &now.hour, &now.minute, &now.second);
Drawer2D_WithoutColors(&str, text);
res = Stream_WriteLine(&logStream, &str);
if (!res) return;
Chat_DisableLogging();
Logger_SysWarn2(res, "writing to", &logPath);
}
void Chat_Add1(const char* format, const void* a1) {
Chat_Add4(format, a1, NULL, NULL, NULL);
}
void Chat_Add2(const char* format, const void* a1, const void* a2) {
Chat_Add4(format, a1, a2, NULL, NULL);
}
void Chat_Add3(const char* format, const void* a1, const void* a2, const void* a3) {
Chat_Add4(format, a1, a2, a3, NULL);
}
void Chat_Add4(const char* format, const void* a1, const void* a2, const void* a3, const void* a4) {
cc_string msg; char msgBuffer[STRING_SIZE * 2];
String_InitArray(msg, msgBuffer);
String_Format4(&msg, format, a1, a2, a3, a4);
Chat_AddOf(&msg, MSG_TYPE_NORMAL);
}
void Chat_AddRaw(const char* raw) {
cc_string str = String_FromReadonly(raw);
Chat_AddOf(&str, MSG_TYPE_NORMAL);
}
void Chat_Add(const cc_string* text) { Chat_AddOf(text, MSG_TYPE_NORMAL); }
void Chat_AddOf(const cc_string* text, int msgType) {
cc_string str;
if (msgType == MSG_TYPE_NORMAL) {
/* Check for chat overflow (see issue #837) */
/* This happens because Offset/Length are packed into a single 32 bit value, */
/* with 9 bits used for length. Hence if offset exceeds 2^23 (8388608), it */
/* overflows and earlier chat messages start wrongly appearing instead */
if (Chat_Log.totalLength > 8388000) {
ClearChatLogs();
Chat_AddRaw("&cChat log cleared as it hit 8.3 million character limit");
}
/* StringsBuffer_Add will abort game if try to add string > 511 characters */
str = *text;
str.length = min(str.length, DRAWER2D_MAX_TEXT_LENGTH);
Chat_GetLogTime(Chat_Log.count) = Game.Time;
AppendChatLog(&str);
StringsBuffer_Add(&Chat_Log, &str);
} else if (msgType >= MSG_TYPE_STATUS_1 && msgType <= MSG_TYPE_STATUS_3) {
/* Status[0] is for texture pack downloading message */
/* Status[1] is for reduced performance mode message */
String_Copy(&Chat_Status[2 + (msgType - MSG_TYPE_STATUS_1)], text);
} else if (msgType >= MSG_TYPE_BOTTOMRIGHT_1 && msgType <= MSG_TYPE_BOTTOMRIGHT_3) {
String_Copy(&Chat_BottomRight[msgType - MSG_TYPE_BOTTOMRIGHT_1], text);
} else if (msgType == MSG_TYPE_ANNOUNCEMENT) {
String_Copy(&Chat_Announcement, text);
Chat_AnnouncementReceived = Game.Time;
} else if (msgType == MSG_TYPE_BIGANNOUNCEMENT) {
String_Copy(&Chat_BigAnnouncement, text);
Chat_BigAnnouncementReceived = Game.Time;
} else if (msgType == MSG_TYPE_SMALLANNOUNCEMENT) {
String_Copy(&Chat_SmallAnnouncement, text);
Chat_SmallAnnouncementReceived = Game.Time;
} else if (msgType >= MSG_TYPE_CLIENTSTATUS_1 && msgType <= MSG_TYPE_CLIENTSTATUS_2) {
String_Copy(&Chat_ClientStatus[msgType - MSG_TYPE_CLIENTSTATUS_1], text);
} else if (msgType >= MSG_TYPE_EXTRASTATUS_1 && msgType <= MSG_TYPE_EXTRASTATUS_2) {
String_Copy(&Chat_Status[msgType - MSG_TYPE_EXTRASTATUS_1], text);
}
Event_RaiseChat(&ChatEvents.ChatReceived, text, msgType);
}
/*########################################################################################################################*
*-------------------------------------------------------Generic chat------------------------------------------------------*
*#########################################################################################################################*/
static void LogInputUsage(const cc_string* text) {
/* Simplify navigating through input history by not logging duplicate entries */
if (Chat_InputLog.count) {
int lastIndex = Chat_InputLog.count - 1;
cc_string last = StringsBuffer_UNSAFE_Get(&Chat_InputLog, lastIndex);
if (String_Equals(text, &last)) return;
}
StringsBuffer_Add(&Chat_InputLog, text);
}
void Chat_Send(const cc_string* text, cc_bool logUsage) {
if (!text->length) return;
Event_RaiseChat(&ChatEvents.ChatSending, text, 0);
if (logUsage) LogInputUsage(text);
if (!Commands_Execute(text)) {
Server.SendChat(text);
}
}
static void OnInit(void) {
#if defined CC_BUILD_MOBILE || defined CC_BUILD_WEB
/* Better to not log chat by default on mobile/web, */
/* since it's not easily visible to end users */
Chat_Logging = Options_GetBool(OPT_CHAT_LOGGING, false);
#else
Chat_Logging = Options_GetBool(OPT_CHAT_LOGGING, true);
#endif
}
static void ClearCPEMessages(void) {
Chat_AddOf(&String_Empty, MSG_TYPE_ANNOUNCEMENT);
Chat_AddOf(&String_Empty, MSG_TYPE_BIGANNOUNCEMENT);
Chat_AddOf(&String_Empty, MSG_TYPE_SMALLANNOUNCEMENT);
Chat_AddOf(&String_Empty, MSG_TYPE_STATUS_1);
Chat_AddOf(&String_Empty, MSG_TYPE_STATUS_2);
Chat_AddOf(&String_Empty, MSG_TYPE_STATUS_3);
Chat_AddOf(&String_Empty, MSG_TYPE_BOTTOMRIGHT_1);
Chat_AddOf(&String_Empty, MSG_TYPE_BOTTOMRIGHT_2);
Chat_AddOf(&String_Empty, MSG_TYPE_BOTTOMRIGHT_3);
}
static void OnReset(void) {
CloseLogFile();
ResetLogFile();
ClearCPEMessages();
}
static void OnFree(void) {
CloseLogFile();
ClearCPEMessages();
ClearChatLogs();
StringsBuffer_Clear(&Chat_InputLog);
}
struct IGameComponent Chat_Component = {
OnInit, /* Init */
OnFree, /* Free */
OnReset /* Reset */
};
|