Compare commits

..

No commits in common. "master" and "1.0.0.18" have entirely different histories.

10 changed files with 317 additions and 147 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1 @@
#include "script_component.hpp"
addMissionEventHandler ["Ended", {
"ArmaRAMDb" callExtension ["save", [true]];
INFO("Mission ended - forced save with backup");
}];
#include "script_component.hpp"

View File

@ -1,4 +1,4 @@
#define MAJOR 1
#define MINOR 0
#define PATCH 0
#define BUILD 19
#define BUILD 18

View File

@ -19,9 +19,6 @@ namespace ArmaRAMDb
{
private const string ARDB_VERSION = "1.0.0";
public const int ARDB_BUFFERSIZE = 10240;
#pragma warning disable CA2211 // Non-constant fields should not be visible
public static RAMDb db;
#pragma warning restore CA2211 // Non-constant fields should not be visible
public static readonly string DEFAULT_ARDB_PATH = $"@ramdb{Path.DirectorySeparatorChar}ArmaRAMDb.ardb";
public static string ARDB_LOGFOLDER { get; private set; } = $"{Path.DirectorySeparatorChar}@ramdb{Path.DirectorySeparatorChar}logs";
public static bool ARDB_DEBUG {get; private set; } = false;
@ -84,13 +81,12 @@ namespace ArmaRAMDb
Log($"Config file found! Context Mode: {ARDB_CONTEXTLOG}! Debug Mode: {ARDB_DEBUG}! " +
$"Auto Backup: {RAMDb.AutoBackupEnabled} (every {RAMDb.BackupFrequencyMinutes} min, keep {RAMDb.MaxBackupsToKeep})", "action");
db ??= new RAMDb();
// First, load any existing RDB file
var db = new RAMDb();
if (File.Exists(Path.Combine(Environment.CurrentDirectory, DEFAULT_ARDB_PATH)))
{
db.ImportFromBinary();
db.ImportFromArdb();
ARDB_ISLOADED = true;
Log("Existing ARDB data loaded during initialization", "action");
}
@ -325,7 +321,7 @@ namespace ArmaRAMDb
WriteOutput(output, "Data loaded");
return 100;
case "save":
Save(args, argc);
Save(args);
WriteOutput(output, "Data saved");
return 100;
case "set":
@ -388,23 +384,23 @@ namespace ArmaRAMDb
Marshal.Copy(bytes, 0, (nint)output, bytes.Length);
}
private static void Save(List<string> args, int argc)
private static void Save(List<string> args)
{
db ??= new RAMDb();
var db = new RAMDb();
// Convert string to boolean properly
bool createBackup = argc > 0 &&
bool createBackup = args.Count > 0 &&
(args[0].Trim('"').Equals("true", StringComparison.CurrentCultureIgnoreCase) || args[0].Trim('"') == "1");
db.ExportToBinary(createBackup);
db.ExportToArdb(createBackup);
Log($"Data saved to ARDB{(createBackup ? " with backup" : "")}", "action");
}
private static void Load()
{
db ??= new RAMDb();
var db = new RAMDb();
db.ImportFromBinary();
db.ImportFromArdb();
ARDB_ISLOADED = true;
Log("Data loaded from ARDB", "action");
}
@ -446,7 +442,7 @@ namespace ArmaRAMDb
await HashStore.HashGet(STEAMID, args[0].Trim('"'), args[1].Trim('"'), _uniqueID, args[2].Trim('"'), args[3].Trim('"'));
break;
default:
Log($"Unexpected argument count: {argc} for HashGet operation", "warning");
Main.Log($"Unexpected argument count: {argc} for HashGet operation", "warning");
break;
}
});
@ -469,7 +465,7 @@ namespace ArmaRAMDb
await HashStore.HashGet(args[0].Trim('"'), args[1].Trim('"'), args[2].Trim('"'), _uniqueID, args[3].Trim('"'), args[4].Trim('"'));
break;
default:
Log($"Unexpected argument count: {argc} for HashGetId operation", "warning");
Main.Log($"Unexpected argument count: {argc} for HashGetId operation", "warning");
break;
}
});
@ -492,7 +488,7 @@ namespace ArmaRAMDb
await HashStore.HashGetAllAsync(STEAMID, args[0].Trim('"'), _uniqueID, args[1].Trim('"'), args[2].Trim('"'));
break;
default:
Log($"Unexpected argument count: {argc} for HashGetAll operation", "warning");
Main.Log($"Unexpected argument count: {argc} for HashGetAll operation", "warning");
break;
}
});
@ -515,7 +511,7 @@ namespace ArmaRAMDb
await HashStore.HashGetAllAsync(args[0].Trim('"'), args[1].Trim('"'), _uniqueID, args[2], args[3].Trim('"'));
break;
default:
Log($"Unexpected argument count: {argc} for HashGetAllId operation", "warning");
Main.Log($"Unexpected argument count: {argc} for HashGetAllId operation", "warning");
break;
}
});
@ -559,7 +555,7 @@ namespace ArmaRAMDb
await HashStore.HashSetAsync(STEAMID, args[0].Trim('"'), args[1].Trim('"'));
break;
default:
Log($"Invalid argument count: {argc} for HashSet operation", "warning");
Main.Log($"Invalid argument count: {argc} for HashSet operation", "warning");
break;
}
});
@ -583,7 +579,7 @@ namespace ArmaRAMDb
await HashStore.HashSetAsync(args[0].Trim('"'), args[1].Trim('"'), args[2].Trim('"'));
break;
default:
Log($"Invalid argument count: {argc} for HashSetId operation", "warning");
Main.Log($"Invalid argument count: {argc} for HashSetId operation", "warning");
break;
}
});
@ -605,7 +601,7 @@ namespace ArmaRAMDb
await ListStore.ListAddAsync(args[0].Trim('"'), args[1].Trim('"'));
break;
default:
Log($"Invalid argument count: {argc} for ListAdd operation", "warning");
Main.Log($"Invalid argument count: {argc} for ListAdd operation", "warning");
break;
}
});
@ -647,7 +643,7 @@ namespace ArmaRAMDb
await ListStore.ListIndexAsync(args[0].Trim('"'), args[1].Trim('"'), args[2].Trim('"'), _uniqueID, args[3].Trim('"'), args[4].Trim('"'));
break;
default:
Log($"Unexpected argument count: {argc} for ListIdx operation", "warning");
Main.Log($"Unexpected argument count: {argc} for ListIdx operation", "warning");
break;
}
});
@ -670,7 +666,7 @@ namespace ArmaRAMDb
await ListStore.ListRangeAsync(args[0].Trim('"'), args[1].Trim('"'), args[2].Trim('"'), args[3].Trim('"'), _uniqueID, args[4].Trim('"'), args[5].Trim('"'));
break;
default:
Log($"Unexpected argument count: {argc} for ListRng operation", "warning");
Main.Log($"Unexpected argument count: {argc} for ListRng operation", "warning");
break;
}
});
@ -713,7 +709,7 @@ namespace ArmaRAMDb
await KeyValueStore.GetAsync(args[0].Trim('"'), args[1].Trim('"'), _uniqueID, args[2].Trim('"'), args[3].Trim('"'));
break;
default:
Log($"Unexpected argument count: {argc} for Get operation", "warning");
Main.Log($"Unexpected argument count: {argc} for Get operation", "warning");
break;
}
});
@ -731,7 +727,7 @@ namespace ArmaRAMDb
private static string HandleListBackupsOperation()
{
db ??= new RAMDb();
var db = new RAMDb();
var backups = db.ListBackups();
if (backups.Count > 0)
@ -756,7 +752,7 @@ namespace ArmaRAMDb
if (args.Count < 1) return false;
string backupFileName = args[0].Trim('"');
db ??= new RAMDb();
var db = new RAMDb();
var backups = db.ListBackups();
// Find the full path based on filename
@ -778,7 +774,7 @@ namespace ArmaRAMDb
if (args.Count < 1) return false;
string backupFileName = args[0].Trim('"');
db ??= new RAMDb();
var db = new RAMDb();
var backups = db.ListBackups();
// Find the full path based on filename

View File

@ -4,7 +4,7 @@ using System.Collections.Concurrent;
namespace ArmaRAMDb
#pragma warning restore IDE0130 // Namespace does not match folder structure
{
public class RAMDb(string ardbPath = null) : IDisposable
internal class RAMDb(string ardbPath = null) : IDisposable
{
private readonly string _ardbPath = Path.Combine(Environment.CurrentDirectory, ardbPath ?? Main.DEFAULT_ARDB_PATH);
public static readonly ConcurrentDictionary<string, string> _keyValues = new();
@ -16,7 +16,7 @@ namespace ArmaRAMDb
public static int MaxBackupsToKeep { get; set; } = 10;
private static Timer _backupTimer;
public void ImportFromBinary()
public void ImportFromArdb()
{
try
{
@ -100,7 +100,7 @@ namespace ArmaRAMDb
}
}
public void ExportToBinary(bool createBackup = false)
public void ExportToArdb(bool createBackup = false)
{
try
{
@ -111,7 +111,7 @@ namespace ArmaRAMDb
{
writer.Write(1);
Utils.WriteDataToBinaryWriter(writer);
WriteDataToBinaryWriter(writer);
}
if (createBackup)
@ -126,7 +126,7 @@ namespace ArmaRAMDb
using (var stream = new FileStream(backupPath, FileMode.Create))
using (var writer = new BinaryWriter(stream))
{
Utils.WriteDataToBinaryWriter(writer);
WriteDataToBinaryWriter(writer);
}
Main.Log($"Created backup at: {backupPath}", "debug");
@ -140,6 +140,44 @@ namespace ArmaRAMDb
}
}
private static void WriteDataToBinaryWriter(BinaryWriter writer)
{
// Write KeyValues
writer.Write(_keyValues.Count);
foreach (var pair in _keyValues)
{
writer.Write(pair.Key);
writer.Write(pair.Value);
}
// Write HashTables
writer.Write(_hashTables.Count);
foreach (var table in _hashTables)
{
writer.Write(table.Key);
writer.Write(table.Value.Count);
foreach (var entry in table.Value)
{
writer.Write(entry.Key);
writer.Write(entry.Value);
}
}
// Write Lists
writer.Write(_lists.Count);
foreach (var list in _lists)
{
writer.Write(list.Key);
writer.Write(list.Value.Count);
foreach (var item in list.Value)
{
writer.Write(item);
}
}
}
public List<string> ListBackups()
{
string backupDirectory = Path.Combine(Path.GetDirectoryName(_ardbPath), "backups");
@ -173,7 +211,7 @@ namespace ArmaRAMDb
_hashTables.Clear();
_lists.Clear();
Utils.ReadDataFromBinaryReader(reader);
ReadDataFromBinaryReader(reader);
Main.Log($"Restored from backup: {backupPath}", "info");
return true;
@ -187,6 +225,62 @@ namespace ArmaRAMDb
return false;
}
private static void ReadDataFromBinaryReader(BinaryReader reader)
{
// Read KeyValues
int keyValueCount = reader.ReadInt32();
for (int i = 0; i < keyValueCount; i++)
{
string key = reader.ReadString();
string value = reader.ReadString();
_keyValues.TryAdd(key, value);
Main.Log($"Loaded key-value: {key} = {value[..Math.Min(50, value.Length)]}...", "debug");
}
// Read HashTables
int tableCount = reader.ReadInt32();
for (int i = 0; i < tableCount; i++)
{
string tableName = reader.ReadString();
Main.Log($"Loading table: {tableName}", "debug");
var concurrentDict = new ConcurrentDictionary<string, string>();
int entryCount = reader.ReadInt32();
for (int j = 0; j < entryCount; j++)
{
string key = reader.ReadString();
string value = reader.ReadString();
concurrentDict.TryAdd(key, value);
Main.Log($"Loaded entry: {key} = {value[..Math.Min(50, value.Length)]}...", "debug");
}
_hashTables.TryAdd(tableName, concurrentDict);
}
// Read Lists
int listCount = reader.ReadInt32();
for (int i = 0; i < listCount; i++)
{
string listName = reader.ReadString();
Main.Log($"Loading list: {listName}", "debug");
var items = new List<string>();
int itemCount = reader.ReadInt32();
for (int j = 0; j < itemCount; j++)
{
string value = reader.ReadString();
items.Add(value);
Main.Log($"Loaded item: {value[..Math.Min(50, value.Length)]}...", "debug");
}
_lists.TryAdd(listName, items);
}
Main.Log("ARDB import complete", "debug");
}
public static void InitializeAutoBackup()
{
if (AutoBackupEnabled)
@ -205,7 +299,8 @@ namespace ArmaRAMDb
{
try
{
Main.db.ExportToBinary(true);
var db = new RAMDb();
db.ExportToArdb(true);
ManageBackupRotation();
@ -221,7 +316,8 @@ namespace ArmaRAMDb
{
try
{
var backups = Main.db.ListBackups();
var db = new RAMDb();
var backups = db.ListBackups();
if (backups.Count > MaxBackupsToKeep)
{
@ -240,17 +336,8 @@ namespace ArmaRAMDb
public void Dispose()
{
try
{
ExportToBinary(true);
_backupTimer?.Dispose();
Main.Log("RAMDb disposed with final save and backup", "action");
}
catch (Exception ex)
{
Main.Log($"Error during final save on disposal: {ex.Message}", "error");
}
GC.SuppressFinalize(this);
_backupTimer?.Dispose();
ExportToArdb(createBackup: true);
}
}
}

View File

@ -1,5 +1,4 @@
using System.Text;
using System.Collections.Concurrent;
#pragma warning disable IDE0130 // Namespace does not match folder structure
namespace ArmaRAMDb
@ -45,100 +44,6 @@ namespace ArmaRAMDb
return DateTimeOffset.Now.ToUnixTimeMilliseconds();
}
public static void WriteDataToBinaryWriter(BinaryWriter writer)
{
// Write KeyValues
writer.Write(RAMDb._keyValues.Count);
foreach (var pair in RAMDb._keyValues)
{
writer.Write(pair.Key);
writer.Write(pair.Value);
}
// Write HashTables
writer.Write(RAMDb._hashTables.Count);
foreach (var table in RAMDb._hashTables)
{
writer.Write(table.Key);
writer.Write(table.Value.Count);
foreach (var entry in table.Value)
{
writer.Write(entry.Key);
writer.Write(entry.Value);
}
}
// Write Lists
writer.Write(RAMDb._lists.Count);
foreach (var list in RAMDb._lists)
{
writer.Write(list.Key);
writer.Write(list.Value.Count);
foreach (var item in list.Value)
{
writer.Write(item);
}
}
}
public static void ReadDataFromBinaryReader(BinaryReader reader)
{
// Read KeyValues
int keyValueCount = reader.ReadInt32();
for (int i = 0; i < keyValueCount; i++)
{
string key = reader.ReadString();
string value = reader.ReadString();
RAMDb._keyValues.TryAdd(key, value);
Main.Log($"Loaded key-value: {key} = {value[..Math.Min(50, value.Length)]}...", "debug");
}
// Read HashTables
int tableCount = reader.ReadInt32();
for (int i = 0; i < tableCount; i++)
{
string tableName = reader.ReadString();
Main.Log($"Loading table: {tableName}", "debug");
var concurrentDict = new ConcurrentDictionary<string, string>();
int entryCount = reader.ReadInt32();
for (int j = 0; j < entryCount; j++)
{
string key = reader.ReadString();
string value = reader.ReadString();
concurrentDict.TryAdd(key, value);
Main.Log($"Loaded entry: {key} = {value[..Math.Min(50, value.Length)]}...", "debug");
}
RAMDb._hashTables.TryAdd(tableName, concurrentDict);
}
// Read Lists
int listCount = reader.ReadInt32();
for (int i = 0; i < listCount; i++)
{
string listName = reader.ReadString();
Main.Log($"Loading list: {listName}", "debug");
var items = new List<string>();
int itemCount = reader.ReadInt32();
for (int j = 0; j < itemCount; j++)
{
string value = reader.ReadString();
items.Add(value);
Main.Log($"Loaded item: {value[..Math.Min(50, value.Length)]}...", "debug");
}
RAMDb._lists.TryAdd(listName, items);
}
Main.Log("ARDB import complete", "debug");
}
public static void CheckByteCount(string uniqueId, string data, string function, string entity, bool call, int bufferSize)
{
if (Encoding.UTF8.GetByteCount(data) <= bufferSize)

187
tools/sqf_validator.py Normal file
View File

@ -0,0 +1,187 @@
#!/usr/bin/env python3
import fnmatch
import os
import re
import sys
def valid_keyword_after_code(content, index):
for word in ["for", "do", "count", "each", "forEach", "else", "and", "not", "isEqualTo", "isNotEqualTo", "in", "call", "spawn", "execVM", "catch", "param", "select", "apply", "findIf", "remoteExec"]:
if content.find(word, index, index + len(word)) != -1:
return True
return False
def check_sqf(filepath):
errors = []
with open(filepath, "r", encoding = "utf-8", errors = "ignore") as file:
content = file.read()
# Store all brackets we find in this file, so we can validate everything on the end
brackets = []
# Used in case we are in a line comment (//)
ignore_till_eol = False
# To check if we are in a comment block
in_comment_block = False
check_if_comment = False
# Used in case we are in a comment block (/* */)
# This is true if we detect a * inside a comment block
# If the next character is a /, it means we end our comment block
check_if_closing = False
# We ignore everything inside a string
in_string = False
# Used to store the starting type of a string, so we can match that to the end of a string
string_type = ""
# Used to check for semicolon after code blocks
last_is_curly_brace = False
check_for_semicolon = False
# Extra information so we know what line we find errors at
line_number = 1
char_index = 0
for c in content:
if last_is_curly_brace:
last_is_curly_brace = False
# Test generates false positives with binary commands that take CODE as 2nd arg (e.g. findIf)
check_for_semicolon = not re.search("findIf", content, re.IGNORECASE)
# Keep track of current line number
if c == "\n":
line_number += 1
# While we are in a string, we can ignore everything else, except the end of the string
if in_string:
if c == string_type:
in_string = False
# Look for the end of this comment block
elif in_comment_block:
if c == "*":
check_if_closing = True
elif check_if_closing:
if c == "/":
in_comment_block = False
elif c != "*":
check_if_closing = False
# If we are not in a comment block, we will check if we are at the start of one or count the () {} and []
else:
# This means we have encountered a /, so we are now checking if this is an inline comment or a comment block
if check_if_comment:
check_if_comment = False
# If the next character after / is a *, we are at the start of a comment block
if c == "*":
in_comment_block = True
# Otherwise, check if we are in an line comment, / followed by another / (//)
elif c == "/":
ignore_till_eol = True
if not in_comment_block:
if ignore_till_eol:
# We are in a line comment, just continue going through the characters until we find an end of line
if c == "\n":
ignore_till_eol = False
else:
if c == '"' or c == "'":
in_string = True
string_type = c
elif c == "/":
check_if_comment = True
elif c == "\t":
errors.append(" ERROR: Found a tab on line {}.".format(line_number))
elif c in ["(", "[", "{"]:
brackets.append(c)
elif c == ")":
if not brackets or brackets[-1] in ["[", "{"]:
errors.append(" ERROR: Missing parenthesis '(' on line {}.".format(line_number))
brackets.append(c)
elif c == "]":
if not brackets or brackets[-1] in ["(", "{"]:
errors.append(" ERROR: Missing square bracket '[' on line {}.".format(line_number))
brackets.append(c)
elif c == "}":
last_is_curly_brace = True
if not brackets or brackets[-1] in ["(", "["]:
errors.append(" ERROR: Missing curly brace '{{' on line {}.".format(line_number))
brackets.append(c)
if check_for_semicolon:
# Keep reading until no white space or comments
if c not in [" ", "\t", "\n", "/"]:
check_for_semicolon = False
if c not in ["]", ")", "}", ";", ",", "&", "!", "|", "="] and not valid_keyword_after_code(content, char_index):
errors.append(" ERROR: Possible missing semicolon ';' on line {}.".format(line_number))
char_index += 1
# Compare opening and closing bracket counts
if brackets.count("(") != brackets.count(")"):
errors.append(" ERROR: Unequal number of parentheses, '(' = {}, ')' = {}.".format(brackets.count("("), brackets.count(")")))
if brackets.count("[") != brackets.count("]"):
errors.append(" ERROR: Unequal number of square brackets, '[' = {}, ']' = {}.".format(brackets.count("["), brackets.count("]")))
if brackets.count("{") != brackets.count("}"):
errors.append(" ERROR: Unequal number of curly braces, '{{' = {}, '}}' = {}.".format(brackets.count("{"), brackets.count("}")))
# Ensure includes are before block comments
if re.compile('\s*(/\*[\s\S]+?\*/)\s*#include').match(content):
errors.append(" ERROR: Found an #include after a block comment.")
return errors
def main():
print("Validating SQF")
print("--------------")
# Allow running from root directory and tools directory
root_dir = ".."
if os.path.exists("addons"):
root_dir = "."
# Check all SQF files in the project directory
sqf_files = []
for root, _, files in os.walk(root_dir):
for file in fnmatch.filter(files, "*.sqf"):
sqf_files.append(os.path.join(root, file))
sqf_files.sort()
bad_count = 0
for filepath in sqf_files:
errors = check_sqf(filepath)
if errors:
print("\nFound {} error(s) in {}:".format(len(errors), os.path.relpath(filepath, root_dir)))
for error in errors:
print(error)
bad_count += 1
print("\nChecked {} files, found errors in {}.".format(len(sqf_files), bad_count))
if bad_count == 0:
print("SQF Validation PASSED")
else:
print("SQF Validation FAILED")
return bad_count
if __name__ == "__main__":
sys.exit(main())