summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nameblocker.sp242
1 files changed, 153 insertions, 89 deletions
diff --git a/nameblocker.sp b/nameblocker.sp
index 366abf9..554b086 100644
--- a/nameblocker.sp
+++ b/nameblocker.sp
@@ -1,4 +1,22 @@
1// Notes:
2 // Memory allocated via the "new" keyword IS garbage collected. Handles are not. Close handles when you're done with them
3 // Handles are closed when the plugin is unloaded, so if the lifetime of a handle is the plugin's lifetime, don't worry about closing it
4 // The contents of ArrayList objects are lost on map transition / server restart. Any persistent data should be stored in a
5 // key-value file OR in a database
6
7 // Basically every string function will add a null terminator, so add `+ 1` to the end of any defined size string definition
8 // It is possible to throw an error with the ThrowError function, but anything that would bother should probably disable the
9 // plugin entirely, so SetFailState is better
10
11 // Large local var allocations if done commonly should be made static. Increases baseline memory usage, but lowers time spent
12 // allocating memory. That being said, don't bother unless it's actually a problem. Name checks should be relatively
13 // infrequent, and everything else exponentially less frequent. Dropping a frame or 2 to allocate 2048 bytes of memory
14 // every century shouldn't be the end of the world
15
16 //
17
1#pragma newdecls required 18#pragma newdecls required
19#pragma semicolon 1
2 20
3#include <sourcemod> 21#include <sourcemod>
4#include <regex> 22#include <regex>
@@ -26,9 +44,6 @@ enum OperatingMode {
26 44
27ArrayList regexlist; 45ArrayList regexlist;
28ArrayList patternlist; 46ArrayList patternlist;
29// Note: Contents of ArrayLists are lost on map change / reload. I should store everything in a database,
30// then use the database to populate the list whenever it is cleared
31
32Database db; 47Database db;
33 48
34ConVar gcvarOperMode; static const char OPERMODENAME[] = "nameblock_OperatingMode"; const OperatingMode DEFAULTOPERMODE = OP_KICK; 49ConVar gcvarOperMode; static const char OPERMODENAME[] = "nameblock_OperatingMode"; const OperatingMode DEFAULTOPERMODE = OP_KICK;
@@ -36,25 +51,69 @@ ConVar gcvarAdmCmdFlag; static const char ADMCMDFLAGNAME[] = "nameb
36ConVar gcvarRegexCompFlags; static const char REGEXCOMPFLAGSNAME[] = "nameblock_RegexCompilationFlags"; const int DEFAULTREGEXCOMPFLAGS = (PCRE_CASELESS | PCRE_DOTALL | PCRE_EXTENDED | PCRE_UTF8); 51ConVar gcvarRegexCompFlags; static const char REGEXCOMPFLAGSNAME[] = "nameblock_RegexCompilationFlags"; const int DEFAULTREGEXCOMPFLAGS = (PCRE_CASELESS | PCRE_DOTALL | PCRE_EXTENDED | PCRE_UTF8);
37ConVar gcvarAmdImmFlag; static const char ADMINIMMUNITYFLAGNAME[] = "nameblock_AdminImmunityFlag"; const int DEFAULTADMIMMFLAG = ADMFLAG_GENERIC; 52ConVar gcvarAmdImmFlag; static const char ADMINIMMUNITYFLAGNAME[] = "nameblock_AdminImmunityFlag"; const int DEFAULTADMIMMFLAG = ADMFLAG_GENERIC;
38 53
54
55
56// Logs and throws an error with the same message of upto 2048 characters
57void logAndFail(const char[] format, any ...) {
58 char buf[2048 + 1];
59 VFormat(buf, sizeof(buf), format, 2);
60
61 LogError(buf);
62 SetFailState(buf);
63}
64
65// Concatenate arguments into a single buffer
66int concatArgs(char[] buf, int maxbuflen, int maxarglen, int end, int start=1) {
67 if(maxbuflen <= 0 || start < 0) return -1;
68
69 char[] arg = new char[maxarglen];
70 char[] tmp = new char[maxbuflen];
71
72 GetCmdArg(start, tmp, maxarglen); // Priming the first argument to prevent weirdness
73 for(int i = start + 1; i <= end; i++) {
74 GetCmdArg(i, arg, maxarglen);
75 Format(tmp, maxbuflen, "%s %s", tmp, arg);
76 }
77
78 return strcopy(buf, maxbuflen, tmp);
79}
80
81// Register a convar who's default value is an integer
39int registerIntConVar(ConVar& cv, int defaultVal, const char[] name, const char[] desc) { 82int registerIntConVar(ConVar& cv, int defaultVal, const char[] name, const char[] desc) {
40 static char tmp[32]; // Consider this a byte array 83 char tmp[32]; // Consider this a byte array
41 Format(tmp, sizeof(tmp), "%d", defaultVal); 84 Format(tmp, sizeof(tmp), "%d", defaultVal);
85
42 cv = CreateConVar(name, tmp, desc); 86 cv = CreateConVar(name, tmp, desc);
87 if(cv == null) return -1;
43 cv.IntValue = defaultVal; 88 cv.IntValue = defaultVal;
44 89
45 return 0; 90 return 0;
46} 91}
47 92
93// `registerIntConVar` wrapper. Calls `logAndFail` if the convar couldn't be registered
94void xRegisterIntConVar(ConVar& cv, int defaultVal, const char[] name, const char[] desc) {
95 if(registerIntConVar(cv, defaultVal, name, desc)) logAndFail("Could not register cvar \"%s\"", name);
96}
97
98
99
48public void OnAllPluginsLoaded() { 100public void OnAllPluginsLoaded() {
49 // Initialize and populate datatypes 101 // Initialize and populate datatypes
50 regexlist = new ArrayList(ByteCountToCells(HANDLE_SIZE)); 102 regexlist = new ArrayList(ByteCountToCells(HANDLE_SIZE));
103 if(regexlist == null) logAndFail("Could not initialize regexlist ArrayList");
104
51 patternlist = new ArrayList(ByteCountToCells(PATTERN_MAX_LEN)); 105 patternlist = new ArrayList(ByteCountToCells(PATTERN_MAX_LEN));
52 // db = 106 if(patternlist == null) logAndFail("Could not initialize patternlist ArrayList");
107
108 char sqlerr[256 + 1];
109 db = SQL_DefConnect(sqlerr, sizeof(sqlerr)); // Default connect until I get a little more acquainted with sm's sql api
110 if(db == null) logAndFail("Could not connect to sql database: %s", sqlerr);
53 111
54 registerIntConVar(gcvarOperMode, view_as<int>(DEFAULTOPERMODE), OPERMODENAME, "Operating mode (disabled, kick, ban, etc.)"); 112 // Register convars
55 registerIntConVar(gcvarAdmCmdFlag, DEFAULTADMCMDFLAG, ADMCMDFLAGNAME, "Admin flag to modify pattern list"); 113 xRegisterIntConVar(gcvarOperMode, view_as<int>(DEFAULTOPERMODE), OPERMODENAME, "Operating mode (disabled, kick, ban, etc.)");
56 registerIntConVar(gcvarRegexCompFlags, DEFAULTREGEXCOMPFLAGS, REGEXCOMPFLAGSNAME, "Regular expression compilation flags"); 114 xRegisterIntConVar(gcvarAdmCmdFlag, DEFAULTADMCMDFLAG, ADMCMDFLAGNAME, "Admin flag to modify pattern list");
57 registerIntConVar(gcvarAmdImmFlag, DEFAULTADMIMMFLAG, ADMINIMMUNITYFLAGNAME, "Admin immunity flag"); 115 xRegisterIntConVar(gcvarRegexCompFlags, DEFAULTREGEXCOMPFLAGS, REGEXCOMPFLAGSNAME, "Regular expression compilation flags");
116 xRegisterIntConVar(gcvarAmdImmFlag, DEFAULTADMIMMFLAG, ADMINIMMUNITYFLAGNAME, "Admin immunity flag");
58 117
59 AutoExecConfig(true, "nameblocker_cvars"); 118 AutoExecConfig(true, "nameblocker_cvars");
60 119
@@ -82,12 +141,14 @@ public Action registerPattern(int client, int args) {
82 } 141 }
83 142
84 char pattern[PATTERN_MAX_LEN]; 143 char pattern[PATTERN_MAX_LEN];
85 GetCmdArg(1, pattern, sizeof(pattern)); 144 if(concatArgs(pattern, sizeof(pattern), PATTERN_MAX_LEN, args) <= 0) {
86 // TODO: Error handling 145 ReplyToCommand(client, "Error: could not get argument list");
146 return Plugin_Handled;
147 }
87 148
88 if(insertPattern(pattern, regexlist.Length - 1)) { 149 if(insertPattern(pattern, regexlist.Length)) {
89 ReplyToCommand(client, "Error: could not register pattern"); 150 ReplyToCommand(client, "Error: could not register pattern");
90 return Plugin_Handled 151 return Plugin_Handled;
91 } 152 }
92 153
93 return Plugin_Handled; 154 return Plugin_Handled;
@@ -99,7 +160,7 @@ public Action deletePattern(int client, int args) {
99 return Plugin_Handled; 160 return Plugin_Handled;
100 } 161 }
101 162
102 int index 163 int index;
103 if(!GetCmdArgIntEx(1, index)) { 164 if(!GetCmdArgIntEx(1, index)) {
104 ReplyToCommand(client, "Error: index argument not numerical"); 165 ReplyToCommand(client, "Error: index argument not numerical");
105 return Plugin_Handled; 166 return Plugin_Handled;
@@ -119,23 +180,25 @@ public Action replacePattern(int client, int args) {
119 return Plugin_Handled; 180 return Plugin_Handled;
120 } 181 }
121 182
122 int index 183 int index;
123 if(!GetCmdArgIntEx(1, index)) { 184 if(!GetCmdArgIntEx(1, index)) {
124 ReplyToCommand(client, "Error: index argument not numerical"); 185 ReplyToCommand(client, "Error: index argument not numerical");
125 return Plugin_Handled; 186 return Plugin_Handled;
126 } 187 }
127 188
128 char pattern[PATTERN_MAX_LEN]; 189 char pattern[PATTERN_MAX_LEN];
129 GetCmdArg(2, pattern, sizeof(pattern)); 190 if(concatArgs(pattern, sizeof(pattern), PATTERN_MAX_LEN, args, 2) <= 0) {
130 // TODO: Error handling 191 ReplyToCommand(client, "Error: could not get argument list");
192 return Plugin_Handled;
193 }
131 194
132 if(removePattern(index)) { 195 if(removePattern(index)) {
133 ReplyToCommand(client, "Error: could not remove pattern"); 196 ReplyToCommand(client, "Error: could not remove pattern");
134 return Plugin_Handled; 197 return Plugin_Handled;
135 } 198 }
136 if(insertPattern(pattern, regexlist.Length - 1)) { 199 if(insertPattern(pattern, index)) {
137 ReplyToCommand(client, "Error: could not register pattern"); 200 ReplyToCommand(client, "Error: could not register pattern");
138 return Plugin_Handled 201 return Plugin_Handled;
139 } 202 }
140 // Preferably this would be atomic as to not lose a pattern, but that's something I can do later 203 // Preferably this would be atomic as to not lose a pattern, but that's something I can do later
141 204
@@ -156,23 +219,25 @@ public Action listPatterns(int client, int args) {
156 219
157 220
158int insertPattern(char pattern[PATTERN_MAX_LEN], int index) { 221int insertPattern(char pattern[PATTERN_MAX_LEN], int index) {
159 if(IsNullString(pattern) || index < 0 || index >= regexlist.Length) return -1; 222 if(IsNullString(pattern) || index < 0 || index > regexlist.Length) return -1;
160 223
161 static char errstr[512]; static RegexError reerr; 224 char errstr[512]; RegexError reerr;
162 Regex res = CompileRegex(pattern, gcvarRegexCompFlags.IntValue, errstr, sizeof(errstr), reerr); 225 Regex res = CompileRegex(pattern, gcvarRegexCompFlags.IntValue, errstr, sizeof(errstr), reerr);
163 if(res == null) { 226 if(res == null) {
164 LogError("Error: Could not compile regex pattern \"%s\": %s (%d)", pattern, errstr, reerr); 227 LogError("Error: Could not compile regex pattern \"%s\": %s (%d)", pattern, errstr, reerr);
165 return -1; 228 return -1;
166 } 229 }
167 230
168 if(index != (regexlist.Length - 1)) { 231 if(index == regexlist.Length) {
232 regexlist.Push(res);
233 patternlist.Push(pattern);
234
235 } else {
169 regexlist.ShiftUp(index); 236 regexlist.ShiftUp(index);
170 patternlist.ShiftUp(index); 237 patternlist.ShiftUp(index);
171 regexlist.Set(index, res); 238 regexlist.Set(index, res);
172 patternlist.Set(index, pattern); 239 patternlist.Set(index, pattern);
173 } else { 240
174 regexlist.Push(res);
175 patternlist.Push(pattern);
176 } 241 }
177 242
178 // TODO: This should also insert the pattern into the database 243 // TODO: This should also insert the pattern into the database
@@ -193,14 +258,15 @@ int removePattern(int index) {
193 258
194 259
195 260
261// Check's a user's name against the regex list. Returns -1 on error, 0 if skipped, 1 on hit
196int checkName(int client) { 262int checkName(int client) {
197 if(client <= 0) return -1; 263 if(client <= 0) return 0;
198 if(gcvarOperMode.IntValue == view_as<int>(OP_DISABLED)) return -1; 264 if(gcvarOperMode.IntValue == view_as<int>(OP_DISABLED)) return 0;
199 if(CheckCommandAccess(client, "", gcvarAmdImmFlag.IntValue, true)) return -1; 265 if(CheckCommandAccess(client, "", gcvarAmdImmFlag.IntValue, true)) return 0;
200 266
201 char name[64 + 1]; 267 char name[64 + 1];
202 if(getName(client, name, sizeof(name)) <= 0) { 268 if(getName(client, name, sizeof(name)) <= 0) {
203 LogError("Tried to get a client's name for a name check, but could not") 269 LogError("Tried to get a client's name for a name check, but could not");
204 return -1; 270 return -1;
205 } 271 }
206 272
@@ -218,7 +284,7 @@ int checkName(int client) {
218 break; 284 break;
219 } 285 }
220 286
221 return 0; 287 return 1;
222} 288}
223 289
224int getName(int client, char[] buf, int buflen) { 290int getName(int client, char[] buf, int buflen) {
@@ -254,7 +320,7 @@ int handleNameHit(int client) {
254} 320}
255 321
256int handleFailedRegex(int client, RegexError reerr) { 322int handleFailedRegex(int client, RegexError reerr) {
257 static char regstr[128]; 323 char regstr[128];
258 RegexStrError(reerr, regstr, sizeof(regstr)); 324 RegexStrError(reerr, regstr, sizeof(regstr));
259 325
260 LogError("Ran into regex error when trying to check user %L's name. Reported regex error: %s", client, regstr); 326 LogError("Ran into regex error when trying to check user %L's name. Reported regex error: %s", client, regstr);
@@ -267,64 +333,62 @@ int handleFailedRegex(int client, RegexError reerr) {
267int RegexStrError(RegexError err, char[] buf, int buflen) { 333int RegexStrError(RegexError err, char[] buf, int buflen) {
268 if(IsNullString(buf)) return -1; 334 if(IsNullString(buf)) return -1;
269 335
336 char tmp[64];
270 switch(err) { 337 switch(err) {
271 case REGEX_ERROR_NONE: {Format(buf, buflen, "No error");} /* No error */ 338 case REGEX_ERROR_NONE: {tmp = "No error";}
272 339
273 case REGEX_ERROR_ASSERT: {Format(buf, buflen, "Internal error");} 340 case REGEX_ERROR_ASSERT: {tmp = "Internal error";}
274 case REGEX_ERROR_BADBR: {Format(buf, buflen, "Invalid repeat counts in {}");} 341 case REGEX_ERROR_BADBR: {tmp = "Invalid repeat counts in {}";}
275 case REGEX_ERROR_BADPAT: {Format(buf, buflen, "Pattern error");} 342 case REGEX_ERROR_BADPAT: {tmp = "Pattern error";}
276 case REGEX_ERROR_BADRPT: {Format(buf, buflen, "? * + invalid");} 343 case REGEX_ERROR_BADRPT: {tmp = "? * + invalid";}
277 case REGEX_ERROR_EBRACE: {Format(buf, buflen, "Unbalanced {}");} 344 case REGEX_ERROR_EBRACE: {tmp = "Unbalanced {}";}
278 case REGEX_ERROR_EBRACK: {Format(buf, buflen, "Unbalanced []");} 345 case REGEX_ERROR_EBRACK: {tmp = "Unbalanced []";}
279 case REGEX_ERROR_ECOLLATE: {Format(buf, buflen, "Collation error - not relevant");} 346 case REGEX_ERROR_ECOLLATE: {tmp = "Collation error - not relevant";}
280 case REGEX_ERROR_ECTYPE: {Format(buf, buflen, "Bad class");} 347 case REGEX_ERROR_ECTYPE: {tmp = "Bad class";}
281 case REGEX_ERROR_EESCAPE: {Format(buf, buflen, "Bad escape sequence");} 348 case REGEX_ERROR_EESCAPE: {tmp = "Bad escape sequence";}
282 case REGEX_ERROR_EMPTY: {Format(buf, buflen, "Empty expression");} 349 case REGEX_ERROR_EMPTY: {tmp = "Empty expression";}
283 case REGEX_ERROR_EPAREN: {Format(buf, buflen, "Unbalanced ()");} 350 case REGEX_ERROR_EPAREN: {tmp = "Unbalanced ()";}
284 case REGEX_ERROR_ERANGE: {Format(buf, buflen, "Bad range inside []");} 351 case REGEX_ERROR_ERANGE: {tmp = "Bad range inside []";}
285 case REGEX_ERROR_ESIZE: {Format(buf, buflen, "Expression too big");} 352 case REGEX_ERROR_ESIZE: {tmp = "Expression too big";}
286 case REGEX_ERROR_ESPACE: {Format(buf, buflen, "Failed to get memory");} 353 case REGEX_ERROR_ESPACE: {tmp = "Failed to get memory";}
287 case REGEX_ERROR_ESUBREG: {Format(buf, buflen, "Bad back reference");} 354 case REGEX_ERROR_ESUBREG: {tmp = "Bad back reference";}
288 case REGEX_ERROR_INVARG: {Format(buf, buflen, "Bad argument");} 355 case REGEX_ERROR_INVARG: {tmp = "Bad argument";}
289 356
290 case REGEX_ERROR_NOMATCH: {Format(buf, buflen, "No match was found");} 357 case REGEX_ERROR_NOMATCH: {tmp = "No match was found";}
291 case REGEX_ERROR_NULL: {Format(buf, buflen, "Null");} 358 case REGEX_ERROR_NULL: {tmp = "Null";}
292 case REGEX_ERROR_BADOPTION: {Format(buf, buflen, "Bad Option");} 359 case REGEX_ERROR_BADOPTION: {tmp = "Bad Option";}
293 case REGEX_ERROR_BADMAGIC: {Format(buf, buflen, "Bad Magic");} 360 case REGEX_ERROR_BADMAGIC: {tmp = "Bad Magic";}
294 case REGEX_ERROR_UNKNOWN_OPCODE: {Format(buf, buflen, "Unknown OpCode");} 361 case REGEX_ERROR_UNKNOWN_OPCODE: {tmp = "Unknown OpCode";}
295 case REGEX_ERROR_NOMEMORY: {Format(buf, buflen, "No Memory");} 362 case REGEX_ERROR_NOMEMORY: {tmp = "No Memory";}
296 case REGEX_ERROR_NOSUBSTRING: {Format(buf, buflen, "No substring");} 363 case REGEX_ERROR_NOSUBSTRING: {tmp = "No substring";}
297 case REGEX_ERROR_MATCHLIMIT: {Format(buf, buflen, "Match limit");} 364 case REGEX_ERROR_MATCHLIMIT: {tmp = "Match limit";}
298 case REGEX_ERROR_CALLOUT: {Format(buf, buflen, "Callout");} // Never used by PCRE itself 365 case REGEX_ERROR_CALLOUT: {tmp = "Callout";} // Never used by PCRE itself
299 case REGEX_ERROR_BADUTF8: {Format(buf, buflen, "Bad UTF8");} 366 case REGEX_ERROR_BADUTF8: {tmp = "Bad UTF8";}
300 case REGEX_ERROR_BADUTF8_OFFSET: {Format(buf, buflen, "Bad UTF8 offset");} 367 case REGEX_ERROR_BADUTF8_OFFSET: {tmp = "Bad UTF8 offset";}
301 case REGEX_ERROR_PARTIAL: {Format(buf, buflen, "Partial");} 368 case REGEX_ERROR_PARTIAL: {tmp = "Partial";}
302 case REGEX_ERROR_BADPARTIAL: {Format(buf, buflen, "Bad Partial");} 369 case REGEX_ERROR_BADPARTIAL: {tmp = "Bad Partial";}
303 case REGEX_ERROR_INTERNAL: {Format(buf, buflen, "Internal error");} 370 case REGEX_ERROR_INTERNAL: {tmp = "Internal error";}
304 case REGEX_ERROR_BADCOUNT: {Format(buf, buflen, "Bad count");} 371 case REGEX_ERROR_BADCOUNT: {tmp = "Bad count";}
305 case REGEX_ERROR_DFA_UITEM: {Format(buf, buflen, "DFA UItem");} 372 case REGEX_ERROR_DFA_UITEM: {tmp = "DFA UItem";}
306 case REGEX_ERROR_DFA_UCOND: {Format(buf, buflen, "DFA UCOND");} 373 case REGEX_ERROR_DFA_UCOND: {tmp = "DFA UCOND";}
307 case REGEX_ERROR_DFA_UMLIMIT: {Format(buf, buflen, "DFA UMLIMIT");} 374 case REGEX_ERROR_DFA_UMLIMIT: {tmp = "DFA UMLIMIT";}
308 case REGEX_ERROR_DFA_WSSIZE: {Format(buf, buflen, "DFA WSSIZE");} 375 case REGEX_ERROR_DFA_WSSIZE: {tmp = "DFA WSSIZE";}
309 case REGEX_ERROR_DFA_RECURSE: {Format(buf, buflen, "DFA recurse");} 376 case REGEX_ERROR_DFA_RECURSE: {tmp = "DFA recurse";}
310 case REGEX_ERROR_RECURSIONLIMIT: {Format(buf, buflen, "Recursion Limit");} 377 case REGEX_ERROR_RECURSIONLIMIT: {tmp = "Recursion Limit";}
311 case REGEX_ERROR_NULLWSLIMIT: {Format(buf, buflen, "NULL WSLIMIT");} /* No longer actually used */ 378 case REGEX_ERROR_NULLWSLIMIT: {tmp = "NULL WSLIMIT";} /* No longer actually used */
312 case REGEX_ERROR_BADNEWLINE: {Format(buf, buflen, "Bad newline");} 379 case REGEX_ERROR_BADNEWLINE: {tmp = "Bad newline";}
313 case REGEX_ERROR_BADOFFSET: {Format(buf, buflen, "Bad offset");} 380 case REGEX_ERROR_BADOFFSET: {tmp = "Bad offset";}
314 case REGEX_ERROR_SHORTUTF8: {Format(buf, buflen, "Short UFT8");} 381 case REGEX_ERROR_SHORTUTF8: {tmp = "Short UFT8";}
315 case REGEX_ERROR_RECURSELOOP: {Format(buf, buflen, "Recurse loop");} 382 case REGEX_ERROR_RECURSELOOP: {tmp = "Recurse loop";}
316 case REGEX_ERROR_JIT_STACKLIMIT: {Format(buf, buflen, "JIT Stacklimit");} 383 case REGEX_ERROR_JIT_STACKLIMIT: {tmp = "JIT Stacklimit";}
317 case REGEX_ERROR_BADMODE: {Format(buf, buflen, "Bad mode");} 384 case REGEX_ERROR_BADMODE: {tmp = "Bad mode";}
318 case REGEX_ERROR_BADENDIANNESS: {Format(buf, buflen, "Bad endianness");} 385 case REGEX_ERROR_BADENDIANNESS: {tmp = "Bad endianness";}
319 case REGEX_ERROR_DFA_BADRESTART: {Format(buf, buflen, "DFA Bad Restart");} 386 case REGEX_ERROR_DFA_BADRESTART: {tmp = "DFA Bad Restart";}
320 case REGEX_ERROR_JIT_BADOPTION: {Format(buf, buflen, "JIT bad option");} 387 case REGEX_ERROR_JIT_BADOPTION: {tmp = "JIT bad option";}
321 case REGEX_ERROR_BADLENGTH: {Format(buf, buflen, "Bad length");} 388 case REGEX_ERROR_BADLENGTH: {tmp = "Bad length";}
322 389
323 default: {Format(buf, buflen, "Unknown Error");} 390 default: {tmp = "Unknown Error";}
324 } 391 }
325 392
326 // Yeah I know this is terrible, and it's entirely because I'm not certain how string assignments work. If this were C I would 393 return strcopy(buf, buflen, tmp);
327 // make a normal char* string and make it point to different text depending on the case
328
329 return 0;
330} \ No newline at end of file 394} \ No newline at end of file