#if defined _dof2_included #endinput #endif #define _dof2_included #include /*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ /* * This is a new version of the INI script Double-O-Files. * However, it's has completely been rewritten and has now a much better performance. * There is also the support for sections in the INI file. (But there is no support for comments.) * Double-O-Files 2 is compatible with DUDB, DINI, Double-O-Files and possibly y_ini since it * can handle sections and entry of the format "key = value", not only "key=value". * The number of spaces between the equal sign and key and value can actually be arbitrary. * I've added some comments below. You may see that I've mentioned the big-O-notation, * 'n' always Entry.Count. * Double-O-Files 2 should also be useful for Russian letter because I'm using * the functions fgetchar and fputchar to write and read the files. * * There is another new feature which has been inspired by ZCMD and y_ini: * The OnParseFile callbacks. To learn more about it, read the description in * the SA-MP forums if you haven't already. * THE END */ /*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ /* native DOF2_SetFile(file[]); native DOF2_LoadFile(); native DOF2_SaveFile(); native DOF2_ParseFile(file[],extraid,bool:callback=true); native DOF2_ReparseFile(file[],extraid,bool:callback=true); native DOF2_WriteFile(); native DOF2_PrintFile(comment[]=""); native DOF2_GetString(file[],key[],tag[]=""); native DOF2_GetStringEx(file[],key[],result[],size,tag[]=""); native Float:DOF2_GetFloat(file[],key[]); native DOF2_GetInt(file[],key[],tag[]=""); native DOF2_GetHex(file[],key[],tag[]=""); native DOF2_GetBin(file[],key[],tag[]=""); native bool:DOF2_GetBool(file[],key[],tag[]=""); native DOF2_SetString(file[],key[],value[],tag[]=""); native DOF2_SetFloat(file[],key[],Float:value); native DOF2_SetInt(file[],key[],value,tag[]=""); native DOF2_SetHex(file[],key[],value,tag[]=""); native DOF2_SetBin(file[],key[],value,tag[]=""); native DOF2_SetBool(file[],key[],bool:value,tag[]=""); native DOF2_IsSet(file[],key[],tag[]=""); native DOF2_Unset(file[],key[],tag[]=""); native DOF2_FileExists(file[]); native DOF2_RemoveFile(file[]); native DOF2_CreateFile(file[],password[]=""); native DOF2_RenameFile(oldfile[],newfile[]); native DOF2_RenameKey(file[],oldkey[],newkey[],tag[]=""); native DOF2_CopyFile(filetocopy[],newfile[]); native DOF2_CheckLogin(file[],password[]); native DOF2_File(user[]); native DOF2_ParseInt(); native DOF2_ParseFloat(); native DOF2_ParseBool(); native DOF2_ParseBin(); native DOF2_ParseHex(); native DOF2_SetUTF8(bool:set); native bool:DOF2_GetUTF8(); native DOF2_GetFile(); native DOF2_MakeBackup(file[]); native DOF2_RemoveTag (file [], tag []); */ // OnParseFile (extraid, value []) // OnParseFile <>(extraid, value []) // OnDefaultParseFile (extraid, value [], key [], tag [], file []) #define OnParseFile<%0><%1>(%2) \ forward _OnParseFile_%0_%1(%2); \ public _OnParseFile_%0_%1(%2) #define OnDefaultParseFile(%0) \ forward _OnDefaultParseFile(%0); \ public _OnDefaultParseFile(%0) #define DOF2_ParseBool() \ (strval (value) || (value [0] && !strcmp (value, "true", true))) #define DOF2_ParseInt() \ (strval (value)) #define DOF2_ParseFloat() \ (floatstr (value)) #define DOF2_ParseBin() \ (DOF2_strbin (value)) #define DOF2_ParseHex() \ (DOF2_strhex (value)) #define DOF2_LoadFile() \ DOF2_ParseFile (CurrentFile, -1, false) #define DOF2_SaveFile \ DOF2_WriteFile #define DOF2_FileExists \ fexist #define Tag. \ Tag_ #define Entry. \ Entry_ #define DOF2:: \ DOF2_ /* #define MAX_TAG_SIZE (32) #define MAX_LINE_SIZE (128) #define MAX_TAGS (32) #define MAX_ENTRIES (256) #define MAX_FILE_SIZE (64) #define USER_FILE_PATH "Users/%s.ini" */ #if !defined MAX_TAG_SIZE #define MAX_TAG_SIZE (32) #endif #if !defined MAX_LINE_SIZE #define MAX_LINE_SIZE (128) #endif #if !defined MAX_TAGS #define MAX_TAGS (32) #endif #if !defined MAX_ENTRIES #define MAX_ENTRIES (256) #endif #if !defined MAX_FILE_SIZE #define MAX_FILE_SIZE (64) #endif #if !defined DUDB_CONVERT && 0 // Change to 1 to enable. #define DUDB_CONVERT #if !defined USER_FILE_PATH #define USER_FILE_PATH "%s.dudb.sav" #endif #elseif !defined USER_FILE_PATH #define USER_FILE_PATH "Users/%s.ini" // Create Users folder or redefine. #endif #if !defined DINI_CONVERT && 0 // Change to 1 to enable. #define DINI_CONVERT #endif #if MAX_TAGS >= 256 #error MAX_TAGS must not be greater than 255. #endif /*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ static stock bool: UTF8 = true, CurrentFile [MAX_FILE_SIZE], bool: FileChanged, Tag.Name [MAX_TAGS][MAX_TAG_SIZE char], Tag.FirstEntry [MAX_TAGS] = {-1, ...}, Tag.LastEntry [MAX_TAGS] = {-1, ...}, Tag.Count, Entry.Line [MAX_ENTRIES][MAX_LINE_SIZE char], Entry.Tag [MAX_ENTRIES][MAX_TAG_SIZE char], Entry.TagID [MAX_ENTRIES char], Entry.NextEntry [MAX_ENTRIES] = {-1, ...}, Entry.PreviousEntry [MAX_ENTRIES] = {-1, ...}, Entry.Count, SortedEntries [MAX_ENTRIES][2]; // Index 0: Hashcode, Index 1: EntryID /*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ DOF2::Exit () DOF2::WriteFile (); stock DOF2::SetUTF8 (bool: set) UTF8 = set; stock bool: DOF2::GetUTF8 () return UTF8; stock DOF2::SetFile (file []) { CurrentFile [0] = '\0'; strcat (CurrentFile, file); } stock DOF2::GetFile () return CurrentFile; stock DOF2::CreateFile (file [], password [] = "") { if (!DOF2::FileExists (file)) { if (password [0]) #if defined DUDB_CONVERT DOF2::SetInt (file, "password_hash", DOF2::num_hash (password)); #else DOF2::SetInt (file, "password", DOF2::num_hash (password)); #endif else { new File: f = fopen (file, io_append); fclose (f); } return 1; } return 0; } stock DOF2::RenameFile (oldfile [], newfile []) { if (DOF2::FileExists (oldfile) && !DOF2::FileExists (newfile)) { if (CurrentFile [0] && !strcmp (CurrentFile, oldfile) && FileChanged) DOF2::WriteFile (); else DOF2::ParseFile (oldfile, -1, false); DOF2::SetFile (newfile); if (DOF2::WriteFile ()) { fremove (oldfile); return 1; } } return 0; } stock DOF2::CopyFile (filetocopy [], newfile []) { if (DOF2::FileExists (filetocopy) && !DOF2::FileExists (newfile)) { if (CurrentFile [0] && !strcmp (CurrentFile, filetocopy) && FileChanged) DOF2::WriteFile (); else DOF2::ParseFile (filetocopy, -1, false); DOF2::SetFile (newfile); return DOF2::WriteFile (); } return 0; } stock DOF2::RemoveFile (file []) { if (file [0]) { if (CurrentFile [0] && !strcmp (CurrentFile, file)) CurrentFile [0] = '\0'; fremove (file); return 1; } return 0; } stock DOF2::MakeBackup (file []) { if (DOF2::FileExists (file)) { new year, month, day, hour, minute, second, backupfile [MAX_FILE_SIZE]; getdate (year, month, day); gettime (hour, minute, second); format (backupfile, sizeof (backupfile), "%s.%02d_%02d_%02d.%02d_%02d_%02d_%02d.bak", CurrentFile, month, day, year, hour, minute, second, GetTickCount ()); return DOF2::CopyFile (CurrentFile, backupfile); } return 0; } stock DOF2::RemoveTag (file [], tag []) { // Removes tag 'tag' with all it's entries. if (file [0] && tag [0]) // You can't remove the empty tag. { if (CurrentFile [0] && !strcmp (CurrentFile, file) && FileChanged) DOF2::WriteFile (); else DOF2::ParseFile (file, -1, false); new line [MAX_LINE_SIZE], buf [MAX_TAG_SIZE], tagid = -1, currententry, key [MAX_KEY_SIZE]; for (new i = 1; i < Tag.Count; ++i) { strunpack (buf, Tag.Name [i]); if (!strcmp (buf, tag)) { tagid = i; break; } } if (tagid != -1) { currententry = Tag.FirstEntry [tagid]; while (currententry != -1) { // Remove all entries under the current tag. strunpack (line, Entry.Line [currententry]); DOF2::ParseLine (line, key, buf); DOF2::Unset (file, key, tag); currententry = Entry.NextEntry [currententry]; } // Move the last tag to the position of the current tag. Creates a little mess. --Tag.Count; Tag.Name [tagid] = Tag.Name [Tag.Count]; Tag.FirstEntry [tagid] = Tag.FirstEntry [Tag.Count]; Tag.LastEntry [tagid] = Tag.LastEntry [Tag.Count]; currententry = Tag.FirstEntry [tagid]; while (currententry != -1) { Entry.TagID {currententry} = tagid; currententry = Entry.NextEntry [currententry]; } FileChanged = true; return 1; } } return 0; } static stock DOF2::FindEntry (key [], tag [], keybuf [], valbuf [], &pos, keybufsize = sizeof (keybuf), valbufsize = sizeof (valbuf)) { if (key [0]) { new entry = -1, l, m, r, h, line [MAX_LINE_SIZE], i; h = DOF2::bernstein (key); l = 0; r = Entry.Count - 1; /* * Binary search in a sorted list of entries in O(log n) time. This algorithm makes for example with 256 elements a maximum of ~8 steps until the entry is found if it exists. * A sequential search would take up to 256 steps. That was the case in the first Double-O-Files script. */ while (l <= r) { if ((r - l) < 2) { if (h == SortedEntries [l][0]) { m = l; entry = SortedEntries [l][1]; } else if (r > l && h == SortedEntries [r][0]) { m = r; entry = SortedEntries [r][1]; } break; } else { m = l + (r - l) / 2; if (h == SortedEntries [m][0]) { entry = SortedEntries [m][1]; break; } else if (h > SortedEntries [m][0]) l = m + 1; else r = m - 1; } } // Candidate found? if (entry != -1) { strunpack (line, Entry.Line [entry]); DOF2::ParseLine (line, keybuf, valbuf, keybufsize, valbufsize); // Check if it's the entry we want. if (!strcmp (keybuf, key) && ((!tag [0] && !Entry.Tag [entry][0]) || (tag [0] && Entry.Tag [entry][0] && !strcmp (tag, Entry.Tag [entry])))) return (pos = m, entry); else { // If not, look left and right in the list for entries with the same hash code. This can be collisions or entries with the same key from another section. for (i = m - 1; i >= 0 && h == SortedEntries [i][0]; --i) { entry = SortedEntries [i][1]; strunpack (line, Entry.Line [entry]); DOF2::ParseLine (line, keybuf, valbuf, keybufsize, valbufsize); if (!strcmp (keybuf, key) && ((!tag [0] && !Entry.Tag [entry][0]) || (tag [0] && Entry.Tag [entry][0] && !strcmp (tag, Entry.Tag [entry])))) return (pos = i, entry); } for (i = m + 1; i < Entry.Count && h == SortedEntries [i][0]; ++i) { entry = SortedEntries [i][1]; strunpack (line, Entry.Line [entry]); DOF2::ParseLine (line, keybuf, valbuf, keybufsize, valbufsize); if (!strcmp (keybuf, key) && ((!tag [0] && !Entry.Tag [entry][0]) || (tag [0] && Entry.Tag [entry][0] && !strcmp (tag, Entry.Tag [entry])))) return (pos = i, entry); } } } } return -1; } stock DOF2::SetString (file [], key [], value [], tag [] = "") { if (file [0] && key [0]) { new entry, pos, tagid = -1, buf [MAX_TAG_SIZE], keybuf [MAX_LINE_SIZE], valbuf [MAX_LINE_SIZE], line [MAX_LINE_SIZE], i; if (!CurrentFile [0] || strcmp (CurrentFile, file)) DOF2::ParseFile (file, -1, false); entry = DOF2::FindEntry (key, tag, keybuf, valbuf, pos); // If the entry has been found, just change it's content. if (entry != -1) { format (line, sizeof (line), "%s = %s", key, value); FileChanged = true; return strpack (Entry.Line [entry], line); } if (Entry.Count >= MAX_ENTRIES) return 0; // Search for the section where the entry belongs. if (!tag [0]) tagid = 0; else { for (i = 1; i < Tag.Count; ++i) { strunpack (buf, Tag.Name [i]); if (buf [0] && !strcmp (tag, buf)) { tagid = i; break; } } } // Section we want does not exist, create new one if possible. if (tagid == -1) { if (Tag.Count >= MAX_TAGS) return 0; tagid = Tag.Count; strpack (Tag.Name [tagid], tag); Tag.FirstEntry [tagid] = Tag.LastEntry [tagid] = -1; ++Tag.Count; } // Add the entry to the section. Section's content is defined by a linear two way list. format (line, sizeof (line), "%s = %s", key, value); strpack (Entry.Line [Entry.Count], line); Entry.Tag [Entry.Count] = Tag.Name [tagid]; Entry.TagID {Entry.Count} = tagid; Entry.NextEntry [Entry.Count] = -1; // Add entry to sorted list of entries and move to right correct position in O(n) time. SortedEntries [Entry.Count][0] = DOF2::bernstein (key); SortedEntries [Entry.Count][1] = Entry.Count; i = Entry.Count - 1; while (i >= 0 && SortedEntries [i][0] > SortedEntries [i + 1][0]) { DOF2::Swap (SortedEntries [i], SortedEntries [i + 1]); --i; } if (Tag.LastEntry [tagid] == -1) { Tag.FirstEntry [tagid] = Tag.LastEntry [tagid] = Entry.Count; Entry.PreviousEntry [Entry.Count] = -1; } else { new name [MAX_TAG_SIZE]; strunpack (name, Tag.Name [tagid]); Entry.NextEntry [Tag.LastEntry [tagid]] = Entry.Count; Entry.PreviousEntry [Entry.Count] = Tag.LastEntry [tagid]; Tag.LastEntry [tagid] = Entry.Count; } ++Entry.Count; FileChanged = true; } return 1; } stock DOF2::GetString (file [], key [], tag [] = "") { new buf [MAX_LINE_SIZE]; DOF2::GetStringEx (file, key, buf, sizeof (buf), tag); return buf; } stock DOF2::GetStringEx (file [], key [], result [], size, tag [] = "") { if (file [0] && key [0]) { new pos, keybuf [MAX_LINE_SIZE]; if (!CurrentFile [0] || strcmp (CurrentFile, file)) DOF2::ParseFile (file, -1, false); // Find entry and assign the result with it's value. return (DOF2::FindEntry (key, tag, keybuf, result, pos, sizeof (keybuf), size) != -1); } return 0; } stock DOF2::Unset (file [], key [], tag [] = "") { if (file [0] && key [0]) { new entry, pos, keybuf [MAX_LINE_SIZE], valbuf [MAX_LINE_SIZE]; if (!CurrentFile [0] || strcmp (CurrentFile, file)) DOF2::ParseFile (file, -1, false); if ((entry = DOF2::FindEntry (key, tag, keybuf, valbuf, pos)) != -1) { // Remove entry from it's section. if (Tag.FirstEntry [Entry.TagID {entry}] == entry) { Tag.FirstEntry [Entry.TagID {entry}] = Entry.NextEntry [entry]; if (Entry.NextEntry [entry] != -1) Entry.PreviousEntry [Entry.NextEntry [entry]] = -1; } else { Entry.NextEntry [Entry.PreviousEntry [entry]] = Entry.NextEntry [entry]; if (Entry.NextEntry [entry] != -1) Entry.PreviousEntry [Entry.NextEntry [entry]] = Entry.PreviousEntry [entry]; } if (Tag.LastEntry [Entry.TagID {entry}] == entry) { Tag.LastEntry [Entry.TagID {entry}] = Entry.PreviousEntry [entry]; if (Entry.PreviousEntry [entry] != -1) Entry.NextEntry [Entry.PreviousEntry [entry]] = -1; } else { Entry.PreviousEntry [Entry.NextEntry [entry]] = Entry.PreviousEntry [entry]; if (Entry.PreviousEntry [entry] != -1) Entry.NextEntry [Entry.PreviousEntry [entry]] = Entry.NextEntry [entry]; } // Move the entry to the end of the sorted list and decrement Entry.Count to forget about the unset entry. while (pos < (Entry.Count - 1)) { DOF2::Swap (SortedEntries [pos], SortedEntries [pos + 1]); ++pos; } --Entry.Count; FileChanged = true; return 1; } } return 0; } stock DOF2::RenameKey (file [], oldkey [], newkey [], tag [] = "") { if (file [0] && oldkey [0]) { new entry, pos, keybuf [MAX_LINE_SIZE], valbuf [MAX_LINE_SIZE], line [MAX_LINE_SIZE]; if (!CurrentFile [0] || strcmp (CurrentFile, file)) DOF2::ParseFile (file, -1, false); if ((entry = DOF2::FindEntry (oldkey, tag, keybuf, valbuf, pos)) != -1) { // Change content of entry. format (line, sizeof (line), "%s = %s", newkey, valbuf); strpack (Entry.Line [entry], line); // Because the hashcode has been changed, the entry has to move in the list. SortedEntries [pos][0] = DOF2::bernstein (newkey); if (pos < (MAX_ENTRIES - 1) && SortedEntries [pos][0] > SortedEntries [pos + 1][0]) { // Hash value of key is greater than the hash value of it's right neighbor, move to the right by swapping the 2 entries. while (pos < (MAX_ENTRIES - 1) && SortedEntries [pos][0] > SortedEntries [pos + 1][0]) { DOF2::Swap (SortedEntries [pos], SortedEntries [pos + 1]); ++pos; } } else if (pos > 0 && SortedEntries [pos][0] < SortedEntries [pos + 1][0]) { // Hash value of key is smaller than the hash value of it' left neighbor, move to the left by swapping the 2 entries. while (pos > 0 && SortedEntries [pos][0] < SortedEntries [pos - 1][0]) { DOF2::Swap (SortedEntries [pos], SortedEntries [pos - 1]); --pos; } } FileChanged = true; return 1; } } return 0; } stock bool: DOF2::IsSet (file [], key [], tag [] = "") { new pos, keybuf [MAX_LINE_SIZE], valbuf [MAX_LINE_SIZE]; if (!CurrentFile [0] || strcmp (CurrentFile, file)) DOF2::ParseFile (file, -1, false); // Try to find the entry. return (DOF2::FindEntry (key, tag, keybuf, valbuf, pos) != -1); } stock DOF2::SetInt (file [], key [], value, tag [] = "") { new buf [16]; format (buf, sizeof (buf), "%d", value); return DOF2::SetString (file, key, buf, tag); } stock DOF2::GetInt (file [], key [], tag [] = "") { new buf [16]; DOF2::GetStringEx (file, key, buf, sizeof (buf), tag); return strval (buf); } stock DOF2::SetHex (file [], key [], value, tag [] = "") { new buf [16]; DOF2::hexstr (value, buf); return DOF2::SetString (file, key, buf, tag); } stock DOF2::GetHex (file [], key [], tag [] = "") { new buf [16]; DOF2::GetStringEx (file, key, buf, sizeof (buf), tag); return DOF2::strhex (buf); } stock DOF2::SetBin (file [], key [], value, tag [] = "") { new buf [35]; DOF2::binstr (value, buf); return DOF2::SetString (file, key, buf, tag); } stock DOF2::GetBin (file [], key [], tag [] = "") { new buf [35]; DOF2::GetStringEx (file, key, buf, sizeof (buf), tag); return DOF2::strbin (buf); } stock DOF2::SetFloat (file [], key [], Float: value, tag [] = "") { new buf [32]; format (buf, sizeof (buf), "%.8f", value); return DOF2::SetString (file, key, buf, tag); } stock Float: DOF2::GetFloat (file [], key [], tag [] = "") { new buf [32]; DOF2::GetStringEx (file, key, buf, sizeof (buf), tag); return floatstr (buf); } stock bool: DOF2::GetBool (file [], key [], tag [] = "") { new buf [16]; DOF2::GetStringEx (file, key, buf, sizeof (buf), tag); return (strval (buf) || (buf [0] && !strcmp (buf, "true", true))); } stock DOF2::SetBool (file [], key [], bool: value, tag [] = "") { if (value) return DOF2::SetString (file, key, "true", tag); else return DOF2::SetString (file, key, "false", tag); } /*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ stock DOF2::PrintFile (comment [] = "") { if (CurrentFile [0]) { new File: f = fopen (CurrentFile, io_write), bool: firstline = true, currententry, buf [MAX_LINE_SIZE], entries, i; if (f) { printf ("[DOF] Current file: %s", CurrentFile); for ( ; i < Tag.Count; ++i) { if (i) { strunpack (buf, Tag.Name [i]); format (buf, sizeof (buf), "[%s]", buf); if (!firstline) print (" "); else firstline = false; print (buf); } currententry = Tag.FirstEntry [i]; while (currententry != -1) { strunpack (buf, Entry.Line [currententry]); currententry = Entry.NextEntry [currententry]; firstline = false; ++entries; print (buf); } } printf ("* %d sections, %d entries", i, entries); if (comment [0]) printf ("* Comment: %s", comment); return fclose (f); } } return 0; } stock DOF2::WriteFile () { if (CurrentFile [0]) { new File: f = fopen (CurrentFile, io_write), bool: firstline = true, currententry; if (f) { for (new i; i < Tag.Count; ++i) { if (Tag.FirstEntry [i] != -1) // Do not write when empty. { if (i) { if (!firstline) { fputchar (f, '\r', UTF8); fputchar (f, '\n', UTF8); } else firstline = false; fputchar (f, '[', UTF8); fwritechars (f, Tag.Name [i]); fputchar (f, ']', UTF8); fputchar (f, '\r', UTF8); fputchar (f, '\n', UTF8); } currententry = Tag.FirstEntry [i]; while (currententry != -1) { fwritechars (f, Entry.Line [currententry]); fputchar (f, '\r', UTF8); fputchar (f, '\n', UTF8); currententry = Entry.NextEntry [currententry]; firstline = false; } } } FileChanged = false; return fclose (f); } } return 0; } stock DOF2::ParseFile (file [], extraid, bool: callback = true) { if (file [0]) { if (((CurrentFile [0] && strcmp (CurrentFile, file)) || callback) && FileChanged) DOF2::WriteFile (); new File: f = fopen (file, io_readwrite), line [MAX_LINE_SIZE char], buf [MAX_LINE_SIZE], key [MAX_LINE_SIZE], value [MAX_LINE_SIZE], tag [MAX_TAG_SIZE], c, pos; if (f) { FileChanged = false; DOF2::SetFile (file); Tag.Count = 1; Entry.Count = 0; Tag.FirstEntry [0] = Tag.LastEntry [0] = -1; for (new i, size = flength (f); i < size; ++i) { c = fgetchar (f, 0, UTF8); if (pos == MAX_LINE_SIZE - 1 || c == '\n' || c == '\r') c = '\0'; line {pos++} = c; if (c == '\0') { // A new section found. Add the section to the list of sections. if (line {0} == '[') { if (Tag.Count < MAX_TAGS) { pos = 1; while (line {pos} && line {pos} != ']' && (pos - 1) < MAX_TAG_SIZE) { Tag.Name [Tag.Count]{pos - 1} = line {pos}; ++pos; } Tag.Name [Tag.Count]{pos - 1} = '\0'; Tag.FirstEntry [Tag.Count] = Tag.LastEntry [Tag.Count] = -1; ++Tag.Count; } } else { if (line {0}) { strunpack (buf, line); DOF2::ParseLine (buf, key, value); strunpack (tag, Tag.Name [Tag.Count - 1]); // Call a specific function for a specific entry - ZCMD-style! if (callback) { format (buf, sizeof (buf), "_OnParseFile_%s_%s", tag, key); if (!CallRemoteFunction (buf, "is", extraid, value)) CallRemoteFunction ("_OnDefaultParseFile", "issss", extraid, value, key, tag, file); } // Add entry to it's section and to the list which will be sorted. Entry.Line [Entry.Count] = line; Entry.Tag [Entry.Count] = Tag.Name [Tag.Count - 1]; Entry.TagID {Entry.Count} = Tag.Count - 1; Entry.NextEntry [Entry.Count] = -1; SortedEntries [Entry.Count][0] = DOF2::bernstein (key); SortedEntries [Entry.Count][1] = Entry.Count; if (Tag.LastEntry [Tag.Count - 1] == -1) { Tag.FirstEntry [Tag.Count - 1] = Tag.LastEntry [Tag.Count - 1] = Entry.Count; Entry.PreviousEntry [Entry.Count] = -1; } else { Entry.NextEntry [Tag.LastEntry [Tag.Count - 1]] = Entry.Count; Entry.PreviousEntry [Entry.Count] = Tag.LastEntry [Tag.Count - 1]; Tag.LastEntry [Tag.Count - 1] = Entry.Count; } ++Entry.Count; } } pos = 0; } } /* * Sort list of entries by it's hashcodes in O(n * log n) time. * (Worst case is actually O(n * n), however, this QuickSort implementation chooses a randomized pivot * to minimize the chance for the worst case.) */ DOF2::QuickSort (SortedEntries, 0, Entry.Count - 1, true); return fclose (f); } } return 0; } stock DOF2::ReparseFile (file [], extraid, bool: callback = true) { if (file [0] && CurrentFile [0] && !strcmp (file, CurrentFile)) { CurrentFile [0] = '\0'; return DOF2::ParseFile (file, extraid, callback); } return 0; } static stock DOF2::ParseLine (line [], key [], value [], keysize = sizeof (key), valuesize = sizeof (value)) { new pos, readpos; if ((pos = charfind (line, '=')) != -1) { // Read key and value. readpos = pos - 1; while (readpos >= 0 && line [readpos] == ' ') --readpos; if (readpos >= 0 && keysize > (readpos + 1)) { key [readpos + 1] = '\0'; while (readpos >= 0) { key [readpos] = line [readpos]; --readpos; } } else return 0; readpos = pos + 1; ++pos; while (line [readpos] == ' ') { ++pos; ++readpos; } if (line [readpos]) { while (readpos >= 0 && line [readpos] && valuesize > (readpos - pos + 1)) { value [readpos - pos] = line [readpos]; ++readpos; } value [readpos - pos] = '\0'; } else { key [0] = '\0'; return 0; } return 1; } return 0; } stock DOF2::File (user []) { new newfile [MAX_FILE_SIZE]; format (newfile, sizeof (newfile), USER_FILE_PATH, DOF2_udb_encode (user)); return newfile; } stock bool: DOF2::CheckLogin (file [], password []) return (file [0] && password [0] && DOF2::num_hash (password) == DOF2::GetInt (file, "password")); /*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ stock DOF2::binstr (value, dest [], size = sizeof (dest)) format (dest, size, "0b%b", value); stock DOF2::hexstr (value, dest [], size = sizeof (dest)) format (dest, size, "0x%x", value); stock DOF2::strhex (string []) { new i, value; if (string [0] == '0' && (string [1] == 'x' || string [1] == 'X')) i = 2; while (string [i]) { value <<= 4; switch (string [i]) { case '0' .. '9': value |= string [i] - '0'; case 'A' .. 'F': value |= string [i] - 'A' + 10; case 'a' .. 'f': value |= string [i] - 'a' + 10; default: return 0; } ++i; } return value; } stock DOF2::strbin (string []) { new i, value; if (string [0] == '0' && (string [1] == 'b' || string [1] == 'B')) i = 2; while (string [i]) { if (string [i] != '1' && string [i] != '0') return 0; value <<= 1; value |= (string [i] - '0'); ++i; } return value; } /*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ static stock charfind (string [], c) { for (new i, len = strlen (string); i < len; ++i) if (string [i] == c) return i; return -1; } static stock fwritechars (File: handle, c []) { new pos; while (c {pos}) fputchar (handle, c {pos++}, UTF8); } static stock DOF2::QuickSort (array [][], l, r, bool: randomize = true) { if (r > l) { if (randomize) { new k = l + (random (32000) % (r - l + 1)); DOF2::Swap (array [k], array [r]); } new i = l - 1, j = r, pivot = array [r][0]; while (i < j) { do ++i; while (array [i][0] <= pivot && i < r); do --j; while (array [j][0] >= pivot && j > l); if (i < j) DOF2::Swap (array [i], array [j]); } DOF2::Swap (array [i], array [r]); DOF2::QuickSort (array, l, i - 1, randomize); DOF2::QuickSort (array, i + 1, r, randomize); } } static stock DOF2::Swap (a [], b []) { new c [2]; c [0] = a [0]; c [1] = a [1]; a [0] = b [0]; a [1] = b [1]; b [0] = c [0]; b [1] = c [1]; } static stock DOF2::bernstein (string []) { new h = -1, i, j; while ((j = string [i++])) h = h * 33 + j; return h; } /*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ stock str_replace (newstr [], oldstr [], srcstr [], deststr [], bool: ignorecase = false, size = sizeof (deststr)) { new newlen = strlen (newstr), oldlen = strlen (oldstr), srclen = strlen (srcstr), idx, rep; for (new i = 0; i < srclen; ++i) { if ((i + oldlen) <= srclen) { if (!strcmp (srcstr [i], oldstr, ignorecase, oldlen)) { deststr [idx] = '\0'; strcat (deststr, newstr, size); ++rep; idx += newlen; i += oldlen - 1; } else { if (idx < (size - 1)) deststr [idx++] = srcstr [i]; else return rep; } } else { if (idx < (size - 1)) deststr [idx++] = srcstr [i]; else return rep; } } deststr [idx] = '\0'; return rep; } stock DOF2::udb_encode (nickname []) { new buf [256], result [256]; static const symbols [][2][] = { {"_", "_00"}, {";", "_01"}, {"!", "_02"}, {"/", "_03"}, {"\\", "_04"}, {"[", "_05"}, {"]", "_06"}, {"?", "_07"}, {".", "_08"}, {"*", "_09"}, {"<", "_10"}, {">", "_11"}, {"{", "_12"}, {"}", "_13"}, {" ", "_14"}, {"\"", "_15"}, {":", "_16"}, {"|", "_17"}, {"=", "_18"} }; strcat (buf, nickname); for (new i = 0; i < sizeof (symbols); ++i) { str_replace (symbols [i][1], symbols [i][0], buf, result); buf [0] = '\0'; strcat (buf, result); } return result; } stock DOF2::udb_decode (nickname []) { new buf [256], result [256]; static const symbols [][2][] = { {"_", "_00"}, {";", "_01"}, {"!", "_02"}, {"/", "_03"}, {"\\", "_04"}, {"[", "_05"}, {"]", "_06"}, {"?", "_07"}, {".", "_08"}, {"*", "_09"}, {"<", "_10"}, {">", "_11"}, {"{", "_12"}, {"}", "_13"}, {" ", "_14"}, {"\"", "_15"}, {":", "_16"}, {"|", "_17"}, {"=", "_18"} }; strcat (buf, nickname); for (new i = 0; i < sizeof (symbols); ++i) { str_replace (symbols [i][0], symbols [i][1], buf, result); buf [0] = '\0'; strcat (buf, result); } return result; } stock DOF2::num_hash (buf []) { new length = strlen (buf), s1 = 1, s2 = 0, n; for (n = 0; n < length; n++) { s1 = (s1 + buf [n]) % 65521; s2 = (s2 + s1) % 65521; } return (s2 << 16) + s1; } /*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ #define DOF_ \ DOF2_ #if defined DUDB_CONVERT #tryinclude #define dUser(%0).( DOF2_GetString(DOF2_File(%0), #define dUserSet(%0).( DOF2_SetString(DOF2_File(%0), #define dUserINT(%0).( DOF2_GetInt(DOF2_File(%0), #define dUserSetINT(%0).( DOF2_SetInt(DOF2_File(%0), #define dUserFLOAT(%0).( DOF2_GetFloat(DOF2_File(%0), #define dUserSetFLOAT(%0).( DOF2_SetFloat(DOF2_File(%0), #define udb_Create(%0,%1) DOF2_CreateFile(DOF2_File(%0),%1) #define udb_RenameUser(%0,%1) DOF2_RenameFile(DOF2_File(%0),DOF2_File(%1)) #define udb_Exists(%0) DOF2_FileExists(DOF2_File(%0)) #define udb_Remove(%0) DOF2_RemoveFile(DOF2_File(%0)) #define udb_CheckLogin(%0,%1) DOF2_CheckLogin(DOF2_File(%0),%1) #if !defined _dudb_included #define _dudb_included #endif #endif #if defined DINI_CONVERT #define dini_Exists DOF2_FileExists #define dini_Remove DOF2_RemoveFile #define dini_Create DOF2_CreateFile #define dini_Set DOF2_SetString #define dini_Get DOF2_GetString #define dini_IntSet DOF2_SetInt #define dini_Int DOF2_GetInt #define dini_BoolSet DOF2_SetBool #define dini_Bool DOF2_GetBool #define dini_FloatSet DOF2_SetFloat #define dini_Float DOF2_GetFloat #define dini_Unset DOF2_Unset #define dini_Isset DOF2_IsSet #if !defined _dini_included #define _dini_included #endif #endif #if defined DINI_CONVERT || defined DUDB_CONVERT #define udb_hash DOF2_num_hash #define num_hash DOF2_num_hash #define udb_encode DOF2_udb_encode #define udb_decode DOF2_udb_decode #endif