diff --git a/addons/spectator/stringtable.xml b/addons/spectator/stringtable.xml
index fbd3311d52..00d50e4704 100644
--- a/addons/spectator/stringtable.xml
+++ b/addons/spectator/stringtable.xml
@@ -9,7 +9,7 @@
Nastavení Pozorovatele
Ajustes de espectador
-
+
Configure how the spectator system will operate by default.
Skonfiguruj domyślne ustawienia obserwatora.
Configura como o sistema de espectador operará por padrão.
diff --git a/tools/search_undefinedFunctions.py b/tools/search_undefinedFunctions.py
new file mode 100644
index 0000000000..1c281fa717
--- /dev/null
+++ b/tools/search_undefinedFunctions.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python3
+
+import fnmatch
+import os
+import re
+import ntpath
+import sys
+import argparse
+
+import ctypes
+
+#from http://stackoverflow.com/a/3429034
+#Get required functions, strcpy..
+strcpy = ctypes.cdll.msvcrt.strcpy
+ocb = ctypes.windll.user32.OpenClipboard #Basic Clipboard functions
+ecb = ctypes.windll.user32.EmptyClipboard
+gcd = ctypes.windll.user32.GetClipboardData
+scd = ctypes.windll.user32.SetClipboardData
+ccb = ctypes.windll.user32.CloseClipboard
+ga = ctypes.windll.kernel32.GlobalAlloc # Global Memory allocation
+gl = ctypes.windll.kernel32.GlobalLock # Global Memory Locking
+gul = ctypes.windll.kernel32.GlobalUnlock
+GMEM_DDESHARE = 0x2000
+
+def Get( ):
+ ocb(None) # Open Clip, Default task
+ pcontents = gcd(1) # 1 means CF_TEXT.. too lazy to get the token thingy ...
+ data = ctypes.c_char_p(pcontents).value
+ #gul(pcontents) ?
+ ccb()
+ return data
+
+def Paste( data ):
+ ocb(None) # Open Clip, Default task
+ ecb()
+ hCd = ga( GMEM_DDESHARE, len( bytes(data,"ascii") )+1 )
+ pchData = gl(hCd)
+ strcpy(ctypes.c_char_p(pchData),bytes(data,"ascii"))
+ gul(hCd)
+ scd(1,hCd)
+ ccb()
+
+
+def getFunctions(filepath):
+ selfmodule = (re.search('addons[\W]*([_a-zA-Z0-9]*)', filepath)).group(1)
+ # print("Checking {0} from {1}".format(filepath,selfmodule))
+
+ with open(filepath, 'r') as file:
+ content = file.read()
+
+ srch = re.compile('[^E]FUNC\(([_a-zA-Z0-9]*)\)')
+ modfuncs = srch.findall(content)
+ modfuncs = sorted(set(modfuncs))
+
+ srch = re.compile('EFUNC\(([_a-zA-Z0-9]*),([_a-zA-Z0-9]*)\)')
+ exfuncs = srch.findall(content)
+ exfuncs = sorted(set(exfuncs))
+
+ fileFuncs = []
+ for func in modfuncs:
+ fileFuncs.append("ace_{0}_fnc_{1}".format(selfmodule,func))
+
+ for exModule,func in exfuncs:
+ fileFuncs.append("ace_{0}_fnc_{1}".format(exModule, func))
+
+ return fileFuncs
+
+
+def getStrings(filepath):
+ selfmodule = (re.search('addons[\W]*([_a-zA-Z0-9]*)', filepath)).group(1)
+ # print("Checking {0} from {1}".format(filepath,selfmodule))
+
+ with open(filepath, 'r') as file:
+ content = file.read()
+
+ srch = re.compile('[^E][CL]STRING\(([_a-zA-Z0-9]*)\)')
+ modStrings = srch.findall(content)
+ modStrings = sorted(set(modStrings))
+
+ srch = re.compile('E[CL]STRING\(([_a-zA-Z0-9]*),([_a-zA-Z0-9]*)\)')
+ exStrings = srch.findall(content)
+ exStrings = sorted(set(exStrings))
+
+ fileStrings = []
+ for localString in modStrings:
+ fileStrings.append("STR_ACE_{0}_{1}".format(selfmodule, localString))
+
+ for (exModule, exString) in exStrings:
+ fileStrings.append("STR_ACE_{0}_{1}".format(exModule, exString))
+
+ return fileStrings
+
+def main():
+
+ print("#########################")
+ print("# All Functions #")
+ print("#########################")
+
+ sqf_list = []
+
+ allFunctions = []
+ allStrings = []
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default=".")
+ args = parser.parse_args()
+
+ for root, dirnames, filenames in os.walk('../addons' + '/' + args.module):
+ for filename in fnmatch.filter(filenames, '*.sqf'):
+ sqf_list.append(os.path.join(root, filename))
+ for filename in fnmatch.filter(filenames, '*.cpp'):
+ sqf_list.append(os.path.join(root, filename))
+ for filename in fnmatch.filter(filenames, '*.hpp'):
+ sqf_list.append(os.path.join(root, filename))
+
+ for filename in sqf_list:
+ allFunctions = allFunctions + getFunctions(filename)
+ for filename in sqf_list:
+ allStrings = allStrings + getStrings(filename)
+
+
+ codeHeader = "diag_log text '*********** Scaning for nil functions [funcs {0} / strings {1}]';".format(len(set(allFunctions)), len(set(allStrings)))
+ codeFuncCheck = "{ if (isNil _x) then {systemChat format ['%1 is nil', _x]; diag_log text format ['%1 is nil', _x];}} forEach allFunctions;"
+ codeStringCheck = "{ if (!isLocalized _x) then {systemChat format ['%1 is not in stringtable', _x]; diag_log text format ['%1 is not in stringtable', _x];}} forEach allStrings;"
+
+ outputCode = "{0} allFunctions = {1}; allStrings = {2}; {3} {4}".format(codeHeader, list(set(allFunctions)), list(set(allStrings)), codeFuncCheck, codeStringCheck)
+
+ print(outputCode)
+ Paste(outputCode);
+
+ print ("")
+ print ("Copied to clipboard, [funcs {0} / strings {1}]'".format(len(set(allFunctions)), len(set(allStrings))))
+
+if __name__ == "__main__":
+ main()