--// Core


return function(Vargs, GetEnv)
	local env = GetEnv(nil, {script = script})
	setfenv(1, env)

	local server = Vargs.Server;
	local service = Vargs.Service;

	local Functions, Admin, Anti, Core, HTTP, Logs, Remote, Process, Variables, Settings, Deps;
	local AddLog, Queue, TrackTask
	local function Init(data)
		Functions = server.Functions;
		Admin = server.Admin;
		Anti = server.Anti;
		Core = server.Core;
		HTTP = server.HTTP;
		Logs = server.Logs;
		Remote = server.Remote;
		Process = server.Process;
		Variables = server.Variables;
		Settings = server.Settings;
		Deps = server.Deps;

		AddLog = Logs.AddLog;
		Queue = service.Queue;
		TrackTask = service.TrackTask;

		--// Core variables
		Core.Themes = data.Themes or {}
		Core.Plugins = data.Plugins or {}
		Core.ModuleID = data.ModuleID or 7510592873
		Core.LoaderID = data.LoaderID or 7510622625
		Core.DebugMode = data.DebugMode or false
		Core.Name = server.Functions:GetRandom()
		Core.LoadstringObj = Core.GetLoadstring()
		Core.Loadstring = require(Core.LoadstringObj)

		service.DataStoreService = require(Deps.MockDataStoreService)

		disableAllGUIs(server.Client.UI);

		Core.Init = nil;
		AddLog("Script", "Core Module Initialized")
	end;

	local function RunAfterPlugins(data)
		--// RemoteEvent Handling
		Core.MakeEvent()

		--// Prepare the client loader
		--local existingPlayers = service.Players:GetPlayers();
		--Core.MakeClient()

		local remoteParent = service.ReplicatedStorage;
		remoteParent.ChildRemoved:Connect(function(c)
			if server.Core.RemoteEvent and not server.Core.FixingEvent and (function() for i,v in pairs(server.Core.RemoteEvent) do if c == v then return true end end end)() then
				wait();
				server.Core.MakeEvent()
			end
		end)

		--// Load data
		Core.DataStore = server.Core.GetDataStore()
		if Core.DataStore then
			TrackTask("Thread: DSLoadAndHook", function()
				pcall(server.Core.LoadData)
			end)
		end

		--// Save all data on server shutdown & set GAME_CLOSING
		game:BindToClose(function()
			Core.GAME_CLOSING = true;
			Core.SaveAllPlayerData();
		end);

		--// Start API
		if service.NetworkServer then
			--service.Threads.RunTask("_G API Manager",server.Core.StartAPI)
			TrackTask("Thread: API Manager", Core.StartAPI)
		end

		--// Occasionally save all player data to the datastore to prevent data loss if the server abruptly crashes
		service.StartLoop("SaveAllPlayerData", Core.DS_AllPlayerDataSaveInterval, Core.SaveAllPlayerData, true)

		Core.RunAfterPlugins = nil;
		AddLog("Script", "Core Module RunAfterPlugins Finished");
	end

	server.Core = {
		Init = Init;
		RunAfterPlugins = RunAfterPlugins;
		DataQueue = {};
		DataCache = {};
		PlayerData = {};
		CrossServerCommands = {};
		CrossServer = function(...) return false end;
		ExecuteScripts = {};
		LastDataSave = 0;
		FixingEvent = false;
		ScriptCache = {};
		Connections = {};
		BytecodeCache = {};
		LastEventValue = 1;

		Variables = {
			TimeBans = {};
		};

		--// Datastore update/queue timers/delays
		DS_WriteQueueDelay = 1;
		DS_ReadQueueDelay = 0.5;
		DS_AllPlayerDataSaveInterval = 30;
		DS_AllPlayerDataSaveQueueDelay = 0.5;

		--// Used to change/"reset" specific datastore keys
		DS_RESET_SALTS = {
			SavedSettings = "32K5j4";
			SavedTables = 	"32K5j4";
		};

		DS_BLACKLIST = {
			Trello_Enabled = true;
			Trello_Primary = true;
			Trello_Secondary = true;
			Trello_Token = true;
			Trello_AppKey = true;

			DataStore = true;
			DataStoreKey = true;
			DataStoreEnabled = true;

			Creators = true;
			Permissions = true;

			G_API = true;
			G_Access = true;
			G_Access_Key = true;
			G_Access_Perms = true;
			Allowed_API_Calls = true;

			LoadAdminsFromDS = true;

			WebPanel_ApiKey = true;
			WebPanel_Enabled = true;

			["Settings.Ranks.Creators.Users"] = true;
			["Admin.SpecialLevels"] = true;

			OnStartup = true;
			OnSpawn = true;
			OnJoin = true;

			CustomRanks = true;
			Ranks = true;

			--// Not gonna let malicious stuff set DS_Blacklist to {} or anything!
			DS_BLACKLIST = true;
		};

		--// Prevent certain keys from loading from the DataStore
		PlayerDataKeyBlacklist = {
			AdminRank = true;
			AdminLevel = true;
			LastLevelUpdate = true;
		};

		DisconnectEvent = function()
			if Core.RemoteEvent and not Core.FixingEvent then
				Core.FixingEvent = true;

				for name,event in pairs(Core.RemoteEvent.Events) do
					event:Disconnect()
				end

				pcall(function() service.Delete(Core.RemoteEvent.Object) end)
				pcall(function() service.Delete(Core.RemoteEvent.Function) end)

				Core.FixingEvent = false;
				Core.RemoteEvent = nil;
			end
		end;

		MakeEvent = function()
			local remoteParent = service.ReplicatedStorage;
			local ran, error = pcall(function()
				if server.Running then
					local rTable = {};
					local event = service.New("RemoteEvent", {Name = Core.Name, Archivable = false}, true, true)
					local func = service.New("RemoteFunction", {Name = "__FUNCTION", Parent = event}, true, true)
					local secureTriggered = true
					local tripDet = math.random()

					local function secure(ev, name, parent)
						return ev.Changed:Connect(function()
							if Core.RemoteEvent == rTable and not secureTriggered then
								if ev == func then
									func.OnServerInvoke = Process.Remote
								end

								if ev.Name ~= name then
									ev.Name = name
								elseif ev.Parent ~= parent then
									secureTriggered = true;
									Core.DisconnectEvent();
									Core.MakeEvent()
								end
							end
						end)
					end

					Core.DisconnectEvent();
					Core.TripDet = tripDet;

					rTable.Events = {};
					rTable.Object = event;
					rTable.Function = func;

					rTable.Events.Security = secure(event, event.Name, remoteParent);
					rTable.Events.FuncSec = secure(func, func.Name, event);

					func.OnServerInvoke = Process.Remote;
					rTable.Events.ProcessEvent = service.RbxEvent(event.OnServerEvent, Process.Remote);

					Core.RemoteEvent = rTable;
					event.Parent = remoteParent;
					secureTriggered = false;

					AddLog(Logs.Script,{
						Text = "Created RemoteEvent";
						Desc = "RemoteEvent was successfully created";
					})
				end
			end)

			if error then
				warn(error)
			end
		end;

		UpdateConnections = function()
			if service.NetworkServer then
				for i,cli in ipairs(service.NetworkServer:GetChildren()) do
					if cli:IsA("NetworkReplicator") then
						Core.Connections[cli] = cli:GetPlayer()
					end
				end
			end
		end;

		UpdateConnection = function(p)
			if service.NetworkServer then
				for i,cli in ipairs(service.NetworkServer:GetChildren()) do
					if cli:IsA("NetworkReplicator") and cli:GetPlayer() == p then
						Core.Connections[cli] = p
					end
				end
			end
		end;

		GetNetworkClient = function(p)
			if service.NetworkServer then
				for i,cli in ipairs(service.NetworkServer:GetChildren()) do
					if cli:IsA("NetworkReplicator") and cli:GetPlayer() == p then
						return cli
					end
				end
			end
		end;

		MakeClient = function(parent)
			if not parent and Core.ClientLoader then
				local loader = Core.ClientLoader;
				loader.Removing = true;

				for i,v in pairs(loader.Events) do
					v:Disconnect()
				end

				loader.Object:Destroy();
			end;

			local depsName = Functions:GetRandom()
			local folder = server.Client:Clone()
			local acli = server.Deps.ClientMover:Clone();
			local client = folder.Client
			local parentObj = parent or service.StarterPlayer:FindFirstChildOfClass("StarterPlayerScripts");
			local clientLoader = {
				Removing = false;
			};

			Core.MockClientKeys = Core.MockClientKeys or {
				Special = depsName;
				Module = client;
			}

			local depsName = Core.MockClientKeys.Special;
			local specialVal = service.New("StringValue")
			specialVal.Value = Core.Name.."\\"..depsName
			specialVal.Name = "Special"
			specialVal.Parent = folder

			acli.Parent = folder;
			acli.Disabled = false;

			folder.Archivable = false;
			folder.Name = depsName; --"Adonis_Client"
			folder.Parent = parentObj;

			if not parent then
				local oName = folder.Name;
				clientLoader.Object = folder;
				clientLoader.Events = {}

				clientLoader.Events[folder] = folder.Changed:Connect(function()
					if Core.ClientLoader == clientLoader and not clientLoader.Removing then
						if folder.Name ~= oName then
							folder.Name = oName;
						elseif folder.Parent ~= parentObj then
							clientLoader.Removing = true;
							Core.MakeClient();
						end
					end
				end)

				local function sec(child)
					local oParent = child.Parent;
					local oName = child.Name;

					clientLoader.Events[child.Changed] = child.Changed:Connect(function(c)
						if Core.ClientLoader == clientLoader and not clientLoader.Removing then
							if child.Parent ~= oParent or child == specialVal then
								Core.MakeClient();
							end
						end
					end)

					local nameEvent = child:GetPropertyChangedSignal("Name"):Connect(function()
						if Core.ClientLoader == clientLoader and not clientLoader.Removing then
							child.Name = oName;
						end
					end)

					clientLoader.Events[nameEvent] = nameEvent;
					clientLoader.Events[child.AncestryChanged] = child.AncestryChanged:Connect(function()
						if Core.ClientLoader == clientLoader and not clientLoader.Removing then
							Core.MakeClient();
						end
					end)
				end;

				for i,child in ipairs(folder:GetDescendants()) do
					sec(child);
				end

				folder.DescendantAdded:Connect(function(d)
					if Core.ClientLoader == clientLoader and not clientLoader.Removing then
						Core.MakeClient();
					end
				end)

				folder.DescendantRemoving:Connect(function(d)
					if Core.ClientLoader == clientLoader and not clientLoader.Removing then
						Core.MakeClient();
					end
				end)

				Core.ClientLoader = clientLoader;
			end


			local ok,err = pcall(function()
				folder.Parent = parentObj
			end)

			clientLoader.Removing = false;

			AddLog("Script", "Created client");
		end;

		HookClient = function(p)
			local key = tostring(p.UserId)
			local keys = Remote.Clients[key]
			if keys then
				local depsName = Functions:GetRandom()
				local eventName = Functions:GetRandom()
				local folder = server.Client:Clone()
				local acli = server.Deps.ClientMover:Clone();
				local client = folder.Client
				local parentTo = "PlayerGui" --// Roblox, seriously, please give the server access to PlayerScripts already so I don't need to do this.
				local parentObj = p:FindFirstChildOfClass(parentTo) or p:WaitForChild(parentTo, 600);

				if not p.Parent then
					return false
				elseif not parentObj then
					p:Kick("\n[CLI-102495] Loading Error \nPlayerGui Missing (Waited 10 Minutes)")
					return false
				end

				local container = service.New("ScreenGui");
				container.ResetOnSpawn = false;
				container.Enabled = false;
				container.Name = "\0";

				local specialVal = service.New("StringValue")
				specialVal.Value = Core.Name.."\\"..depsName
				specialVal.Name = "Special"
				specialVal.Parent = folder

				keys.Special = depsName
				keys.EventName = eventName
				keys.Module = client

				acli.Parent = folder;
				acli.Disabled = false;

				folder.Name = "Adonis_Client"
				folder.Parent = container;

				--// Event only fires AFTER the client is alive and well
				local event; event = service.Events.ClientLoaded:Connect(function(plr)
					if p == plr and container.Parent == parentObj then
						container.Parent = nil --container:Destroy(); -- Destroy update causes an issue with this pretty sure
						p.AncestryChanged:Connect(function() -- after/on remove, not on removing...
							if p.Parent == nil then
								pcall(function() container:Destroy() end) -- Prevent potential memory leak and ensure this gets properly murdered when they leave and it's no longer needed
							end
						end)
						event:Disconnect();
					end
				end)

				local ok,err = pcall(function()
					container.Parent = parentObj
				end)

				if not ok then
					p:Kick("\n[CLI-192385] Loading Error \n[HookClient Error: "..tostring(err).."]")
					return false
				else
					return true
				end
			else
				if p and p.Parent then
					p:Kick("\n[CLI-5691283] Loading Error \n[HookClient: Keys Missing]")
				end
			end
		end;

		LoadClientLoader = function(p)
			local loader = Deps.ClientLoader:Clone()
			loader.Name = Functions.GetRandom()
			loader.Parent = p:WaitForChild("PlayerGui", 60) or p:WaitForChild("Backpack")
			loader.Disabled = false
		end;

		LoadExistingPlayer = function(p)
			warn("Loading existing player: ".. tostring(p))

			TrackTask("Thread: Setup Existing Player: ".. tostring(p), function()
				Process.PlayerAdded(p)
				--Core.MakeClient(p:FindFirstChildOfClass("PlayerGui") or p:WaitForChild("PlayerGui", 120))
			end)
		end;

		ExecutePermission = function(scr, code, isLocal)
			local fixscr = service.UnWrap(scr)

			for _, val in pairs(Core.ExecuteScripts) do
				if not isLocal or (isLocal and val.Type == "LocalScript") then
					if (service.UnWrap(val.Script) == fixscr or code == val.Code) and (not val.runLimit or (val.runLimit ~= nil and val.Executions <= val.runLimit)) then
						val.Executions = val.Executions+1

						return {
							Source = val.Source;
							noCache = val.noCache;
							runLimit = val.runLimit;
							Executions = val.Executions;
						}
					end
				end
			end
		end;

		GetScript = function(scr,code)
			for i,val in pairs(Core.ExecuteScripts) do
				if val.Script == scr or code == val.Code then
					return val,i
				end
			end
		end;

		UnRegisterScript = function(scr)
			for i,dat in pairs(Core.ExecuteScripts) do
				if dat.Script == scr or dat == scr then
					table.remove(Core.ExecuteScripts, i)
					return dat
				end
			end
		end;

		RegisterScript = function(data)
			data.Executions = 0
			data.Time = os.time()
			data.Type = data.Script.ClassName
			data.Wrapped = service.Wrap(data.Script)
			data.Wrapped:SetSpecial("Clone",function()
				return Core.RegisterScript {
					Script = service.UnWrap(data.Script):Clone();
					Code = data.Code;
					Source = data.Source;
					noCache = data.noCache;
					runLimit = data.runLimit;
				}
			end)

			for ind,scr in pairs(Core.ExecuteScripts) do
				if scr.Script == data.Script then
					return scr.Wrapped or scr.Script
				end
			end

			if not data.Code then
				data.Code = Functions.GetRandom()
			end

			table.insert(Core.ExecuteScripts,data)
			return data.Wrapped
		end;

		GetLoadstring = function()
			local newLoad = Deps.Loadstring:Clone();
			local lbi = server.Shared.FiOne:Clone();

			lbi.Parent = newLoad

			return newLoad;
		end;

		Bytecode = function(str)
			if Core.BytecodeCache[str] then return Core.BytecodeCache[str] end
			local f, buff = Core.Loadstring(str)
			Core.BytecodeCache[str] = buff
			return buff
		end;

		NewScript = function(type,source,allowCodes,noCache,runLimit)
			local ScriptType
			local execCode = Functions.GetRandom()

			if type == 'Script' then
				ScriptType = Deps.ScriptBase:Clone()
			elseif type == 'LocalScript' then
				ScriptType = Deps.LocalScriptBase:Clone()
			end

			if ScriptType then
				ScriptType.Name = "[Adonis] ".. type

				if allowCodes then
					service.New("StringValue", {
						Name = "Execute",
						Value = execCode,
						Parent = ScriptType,
					})
				end

				local wrapped = Core.RegisterScript {
					Script = ScriptType;
					Code = execCode;
					Source = Core.Bytecode(source);
					noCache = noCache;
					runLimit = runLimit;
				}

				return wrapped or ScriptType, ScriptType, execCode
			end
		end;

		SavePlayer = function(p,data)
			local key = tostring(p.UserId)
			Core.PlayerData[key] = data
		end;

		DefaultPlayerData = function(p)
			return {
				Donor = {
					Cape = {
						Image = '0';
						Color = 'White';
						Material = 'Neon';
					};
					Enabled = false;
				};
				Banned = false;
				TimeBan = false;
				AdminNotes = {};
				Keybinds = {};
				Aliases = {};
				Client = {};
				Warnings = {};
				AdminPoints = 0;
			};
		end;

		GetPlayer = function(p)
			local key = tostring(p.UserId)

			if not Core.PlayerData[key] then
				local PlayerData = Core.DefaultPlayerData(p)

				Core.PlayerData[key] = PlayerData

				if Core.DataStore then
					local data = Core.GetData(key)
					if data and type(data) == "table" then
						data.AdminNotes = (data.AdminNotes and Functions.DSKeyNormalize(data.AdminNotes, true)) or {}
						data.Warnings = (data.Warnings and Functions.DSKeyNormalize(data.Warnings, true)) or {}

						local BLOCKED_SETTINGS = server.Core.PlayerDataKeyBlacklist

						for i,v in pairs(data) do
							if not BLOCKED_SETTINGS[i] then
								PlayerData[i] = v
							end
						end
					end
				end

				return PlayerData
			else
				return Core.PlayerData[key]
			end
		end;

		ClearPlayer = function(p)
			Core.PlayerData[tostring(p.UserId)] = Core.DefaultData(p);
		end;

		SavePlayerData = function(p, customData)
			local key = tostring(p.UserId);
			local pData = customData or Core.PlayerData[key];

			if Core.DataStore then
				if pData then
					local data = service.CloneTable(pData);

					data.LastChat = nil
					data.AdminRank = nil
					data.AdminLevel = nil
					data.LastLevelUpdate = nil
					data.LastDataSave = nil

					data.AdminNotes = Functions.DSKeyNormalize(data.AdminNotes)
					data.Warnings = Functions.DSKeyNormalize(data.Warnings)

					Core.SetData(key, data)
					AddLog(Logs.Script,{
						Text = "Saved data for ".. p.Name;
						Desc = "Player data was saved to the datastore";
					})

					pData.LastDataSave = os.time();
				end
			end
		end;

		SaveAllPlayerData = function(queueWaitTime)
			local TrackTask = service.TrackTask
			for key,pdata in pairs(Core.PlayerData) do
				local id = tonumber(key);
				local player = id and service.Players:GetPlayerByUserId(id);
				if player and (not pdata.LastDataSave or os.time() - pdata.LastDataSave >= Core.DS_AllPlayerDataSaveInterval)  then
					TrackTask(string.format("Save data for %s", player.Name), Core.SavePlayerData, player);
				end
			end
			--[[ --// OLD METHOD (Kept in case this messes anything up)
			for i,p in next,service.Players:GetPlayers() do
				local pdata = Core.PlayerData[tostring(p.UserId)];
				--// Only save player's data if it has not been saved within the last INTERVAL (default 30s)
				if pdata and (not pdata.LastDataSave or os.time() - pdata.LastDataSave >= Core.DS_AllPlayerDataSaveInterval) then
					service.Queue("SavePlayerData", function()
						Core.SavePlayerData(p)
						wait(queueWaitTime or Core.DS_AllPlayerDataSaveQueueDelay)
					end)
				end
			end--]]
		end;
		GetDataStore = function()
			local ran,store = pcall(function()

				return service.DataStoreService:GetDataStore(string.sub(Settings.DataStore, 1, 50),"Adonis")
			end)

			-- DataStore studio check.
			if ran and store and service.RunService:IsStudio() then
				local success, res = pcall(store.GetAsync, store, math.random())
				if not success and string.find(res, "502", 1, true) then
					warn("Unable to load data because Studio access to API services is disabled.")
					return;
				end
			end

			return ran and store
		end;

		DataStoreEncode = function(key)
			if Core.DS_RESET_SALTS[key] then
				key = Core.DS_RESET_SALTS[key] .. key
			end

			return Functions.Base64Encode(Remote.Encrypt(tostring(key), Settings.DataStoreKey))
		end;

		SaveData = function(...)
			return Core.SetData(...)
		end;

		DS_GetRequestDelay = function(type)
			local requestType, budget;

			local reqPerMin = 60 + #service.Players:GetPlayers() * 10;
			local reqDelay = 60 / reqPerMin;

			if type == "Write" then
				requestType = Enum.DataStoreRequestType.SetIncrementAsync;
			elseif type == "Read" then
				requestType = Enum.DataStoreRequestType.GetAsync;
			elseif type == "Update" then
				requestType = Enum.DataStoreRequestType.UpdateAsync;
			end

			repeat
				budget = service.DataStoreService:GetRequestBudgetForRequestType(requestType);
			until budget > 0 and task.wait(1)

			return reqDelay + 0.5;
		end;

		DS_WriteLimiter = function(type, func, ...)
			local vararg = table.pack(...)
			return Queue("DataStoreWriteData_" .. tostring(type), function()
				local gotDelay = Core.DS_GetRequestDelay(type); --// Wait for budget, also return how long we should wait before the next request is allowed to go
				func(unpack(vararg, 1, vararg.n))
				task.wait(gotDelay)
			end, 120, true)
		end;

		RemoveData = function(key)
			local DataStore = Core.DataStore
			if DataStore then
				local ran2, err2 = Queue("DataStoreWriteData" .. tostring(key), function()
					local ran, ret = Core.DS_WriteLimiter("Write", DataStore.RemoveAsync, DataStore, Core.DataStoreEncode(key))
					if ran then
						Core.DataCache[key] = nil
					else
						logError("DataStore RemoveAsync Failed: ".. tostring(ret))
					end

					task.wait(6)
				end, 120, true)

				if not ran2 then
					warn("DataStore RemoveData Failed: ".. tostring(err2))
				end
			end
		end;

		SetData = function(key, value, repeatCount)
			if repeatCount then
				warn("Retrying SetData request for ".. key);
			end

			local DataStore = Core.DataStore
			if DataStore then
				if value == nil then
					return Core.RemoveData(key)
				else
					local ran2, err2 = Queue("DataStoreWriteData" .. tostring(key), function()
						local ran, ret = Core.DS_WriteLimiter("Write", DataStore.SetAsync, DataStore, Core.DataStoreEncode(key), value)
						if ran then
							Core.DataCache[key] = value
						else
							logError("DataStore SetAsync Failed: ".. tostring(ret));
						end

						task.wait(6)
					end, 120, true)

					if not ran2 then
						logError("DataStore SetData Failed: ".. tostring(err2))

						--// Attempt 3 times, with slight delay between if failed
						task.wait(1);
						if not repeatCount then
							return Core.SetData(key, value, 3);
						elseif repeatCount > 0 then
							return Core.SetData(key, value, repeatCount - 1);
						end
					end
				end
			end
		end;

		UpdateData = function(key, func, repeatCount)
			if repeatCount then
				warn("Retrying UpdateData request for ".. key);
			end

			local DataStore = Core.DataStore
			if DataStore then
				local err = false;
				local ran2, err2 = Queue("DataStoreWriteData" .. tostring(key), function()
					local ran, ret = Core.DS_WriteLimiter("Update", DataStore.UpdateAsync, DataStore, Core.DataStoreEncode(key), func)

					if not ran then
						err = ret;
						logError("DataStore UpdateAsync Failed: ".. tostring(ret))
						return error(ret);
					end

					wait(6)
				end, 120, true) --// 120 timeout, yield until this queued function runs and completes

				if not ran2 then
					logError("DataStore UpdateData Failed: ".. tostring(err2))

					--// Attempt 3 times, with slight delay between if failed
					wait(1);
					if not repeatCount then
						return Core.UpdateData(key, func, 3);
					elseif repeatCount > 0 then
						return Core.UpdateData(key, func, repeatCount - 1);
					end
				end

				return err
			end
		end;

		GetData = function(key, repeatCount)
			if repeatCount then
				warn("Retrying GetData request for ".. key);
			end

			local DataStore = Core.DataStore
			if DataStore then
				local ran2, err2 = Queue("DataStoreReadData", function()
					local ran, ret = pcall(DataStore.GetAsync, DataStore, Core.DataStoreEncode(key))
					if ran then
						Core.DataCache[key] = ret
						return ret
					else
						logError("DataStore GetAsync Failed: ".. tostring(ret))
						if Core.DataCache[key] then
							return Core.DataCache[key];
						else
							error(ret);
						end
					end
					wait(Core.DS_GetRequestDelay("Read"))
				end, 120, true)

				if not ran2 then
					logError("DataStore GetData Failed: ".. tostring(err2))
					--// Attempt 3 times, with slight delay between if failed
					wait(1);
					if not repeatCount then
						return Core.GetData(key, 3);
					elseif repeatCount > 0 then
						return Core.GetData(key, repeatCount - 1);
					end
				else
					return err2;
				end
			end
		end;

		IndexPathToTable = function(tableAncestry)
			local Blacklist = Core.DS_BLACKLIST
			if type(tableAncestry) == "string" and not Blacklist[tableAncestry] then
				return server.Settings[tableAncestry], tableAncestry
			elseif type(tableAncestry) == "table" then
				local curTable = server
				local curName = "Server"

				for _, ind in ipairs(tableAncestry) do
					curTable = curTable[ind]
					curName = ind

					if curName and type(curName) == "string" then
						--// Admins do NOT load from the DataStore with this setting
						if curName == "Ranks" and Settings.LoadAdminsFromDS == false then 
							return nil
						end
					end

					if not curTable then
						--warn(tostring(ind) .." could not be found");
						--// Not allowed or table is not found
						return nil
					end
				end

				if curName and type(curName) == "string" and Blacklist[curName] then
					return nil
				end

				return curTable, curName
			end
			return nil
		end;

		ClearAllData = function()
			local tabs = Core.GetData("SavedTables") or {};

			for _, v in pairs(tabs) do
				if v.TableKey then
					Core.RemoveData(v.TableKey);
				end
			end

			Core.SetData("SavedSettings",{});
			Core.SetData("SavedTables",{});
			Core.CrossServer("LoadData");
		end;

		GetTableKey = function(indList)
			local tabs = Core.GetData("SavedTables") or {};
			local realTable,tableName = Core.IndexPathToTable(indList);

			local foundTable = nil;

			for i,v in pairs(tabs) do
				if type(v) == "table" and v.TableName and v.TableName == tableName then
					foundTable = v
					break;
				end
			end

			if not foundTable then
				foundTable = {
					TableName = tableName;
					TableKey = "SAVEDTABLE_".. tableName;
				}

				table.insert(tabs, foundTable);
				Core.SetData("SavedTables", tabs);
			end

			if not Core.GetData(foundTable.TableKey) then
				Core.SetData(foundTable.TableKey, {});
			end

			return foundTable.TableKey;
		end;

		DoSave = function(data)
			local type = data.Type
			if type == "ClearSettings" then
				Core.ClearAllData();
			elseif type == "SetSetting" then
				local setting = data.Setting
				local value = data.Value

				Core.UpdateData("SavedSettings", function(settings)
					settings[setting] = value
					return settings
				end)

				Core.CrossServer("LoadData", "SavedSettings", {[setting] = value});
			elseif type == "TableRemove" then
				local key = Core.GetTableKey(data.Table);
				local tab = data.Table
				local value = data.Value

				data.Action = "Remove"
				data.Time = os.time()

				local CheckMatch = Functions.CheckMatch
				Core.UpdateData(key, function(sets)
					sets = sets or {}

					for i,v in pairs(sets) do
						if CheckMatch(tab, v.Table) and CheckMatch(v.Value, value) then
							table.remove(sets,i)
						end
					end

					--// Check that the real table actually has the item to remove, do not create if it does not have it
					--// Prevents snowballing
					local indList = tab
					local continueOperation = false
					if indList[1] == "Settings" then
						local indClone = table.clone(indList)
						indClone[1] = "OriginalSettings"
						local realTable,tableName = Core.IndexPathToTable(indClone)
						for i,v in pairs(realTable) do
							if CheckMatch(v, value) then
								continueOperation = true
							end
						end
					else
						continueOperation = true
					end

					if continueOperation then
						table.insert(sets, data)
					end

					return sets
				end)

				Core.CrossServer("LoadData", "TableUpdate", data);
			elseif type == "TableAdd" then
				local key = Core.GetTableKey(data.Table);
				local tab = data.Table
				local value = data.Value

				data.Action = "Add"
				data.Time = os.time()

				local CheckMatch = Functions.CheckMatch
				Core.UpdateData(key, function(sets)
					sets = sets or {}

					for i,v in pairs(sets) do
						if CheckMatch(tab, v.Table) and CheckMatch(v.Value, value) then
							table.remove(sets, i)
						end
					end

					--// Check that the real table does not have the item to add, do not create if it has it
					--// Prevents snowballing
					local indList = tab
					local continueOperation = true
					if indList[1] == "Settings" then
						local indClone = table.clone(indList)
						indClone[1] = "OriginalSettings"
						local realTable,tableName = Core.IndexPathToTable(indClone)
						for i,v in pairs(realTable) do
							if CheckMatch(v, value) then
								continueOperation = false
							end
						end
					end

					if continueOperation then
						table.insert(sets, data)
					end

					return sets
				end)

				Core.CrossServer("LoadData", "TableUpdate", data);
			end

			AddLog(Logs.Script,{
				Text = "Saved setting change to datastore";
				Desc = "A setting change was issued and saved";
			})
		end;

		LoadData = function(key, data, serverId)
			if serverId and serverId == game.JobId then return end;

			local Blacklist = Core.DS_BLACKLIST
			local CheckMatch = Functions.CheckMatch;
			if key == "TableUpdate" then
				local tab = data;
				local indList = tab.Table;
				local nameRankComp = {--// Old settings backwards compatability
					Owners = {"Settings", "Ranks", "HeadAdmins", "Users"};
					Creators = {"Settings", "Ranks", "Creators", "Users"};
					HeadAdmins = {"Settings", "Ranks", "HeadAdmins", "Users"};
					Admins = {"Settings", "Ranks", "Admins", "Users"};
					Moderators = {"Settings", "Ranks", "Moderators", "Users"};
				}

				if type(indList) == "string" and nameRankComp[indList] then
					indList = nameRankComp[indList];
				end

				local realTable, tableName = Core.IndexPathToTable(indList);
				local displayName = type(indList) == "table" and table.concat(indList, ".") or tableName;

				if displayName and type(displayName) == "string" then
					if Blacklist[displayName] then 
						return
					end 
					if type(indList) == "table" and indList[1] == "Settings" and indList[2] == "Ranks" then
						if not Settings.SaveAdmins and not Core.WarnedAboutAdminsLoadingWhenSaveAdminsIsOff and not Settings.SaveAdminsWarning and Settings.LoadAdminsFromDS then
							warn("Admins are loading from the Adonis DataStore when Settings.SaveAdmins is FALSE!\nDisable this warning by adding the setting \"SaveAdminsWarning\" in Settings (and set it to true!) or set Settings.LoadAdminsFromDS to false")
							Core.WarnedAboutAdminsLoadingWhenSaveAdminsIsOff = true
						end
					end
				end

				if realTable and tab.Action == "Add" then
					for i,v in pairs(realTable) do
						if CheckMatch(v, tab.Value) then
							table.remove(realTable, i)
						end
					end

					AddLog("Script", {
						Text = "Added value to ".. displayName;
						Desc = "Added "..tostring(tab.Value).." to ".. displayName .." from datastore";
					})

					table.insert(realTable, tab.Value)
				elseif realTable and tab.Action == "Remove" then
					for i,v in pairs(realTable) do
						if CheckMatch(v, tab.Value) then
							AddLog("Script",{
								Text = "Removed value from ".. displayName;
								Desc = "Removed "..tostring(tab.Value).." from ".. displayName .." from datastore";
							})

							table.remove(realTable, i)
						end
					end
				end
			else
				local SavedSettings
				local SavedTables
				if Core.DataStore and Settings.DataStoreEnabled then
					if Settings.DataStoreKey == server.Defaults.Settings.DataStoreKey then
						table.insert(server.Messages, {
							Title = "Warning!";
							Message = "Using default datastore key!";
							Icon = server.MatIcons.Description;
							Time = 15;
							OnClick = server.Core.Bytecode([[
								local window = client.UI.Make("Window", {
									Title = "How to change the DataStore key";
									Size = {700,300};
									Icon = "rbxassetid://7510994359";
								})

								window:Add("ImageLabel", {
									Image = "rbxassetid://1059543904";
								})

								window:Ready()
							]]);
						})
					end
					local GetData, LoadData, SaveData, DoSave = Core.GetData, Core.LoadData, Core.SaveData, Core.DoSave

					if not key then
						SavedSettings = GetData("SavedSettings")
						SavedTables = GetData("SavedTables")
					elseif key and not data then
						if key == "SavedSettings" then
							SavedSettings = GetData("SavedSettings")
						elseif key == "SavedTables" then
							SavedTables = GetData("SavedTables")
						end
					elseif key and data then
						if key == "SavedSettings" then
							SavedSettings = data
						elseif key == "SavedTables" then
							SavedTables = data
						end
					end

					if not key and not data then
						if not SavedSettings then
							SavedSettings = {}
							SaveData("SavedSettings", {})
						end

						if not SavedTables then
							SavedTables = {}
							SaveData("SavedTables", {})
						end
					end

					if SavedSettings then
						for setting,value in pairs(SavedSettings) do
							if not Blacklist[setting] then
								if setting == 'Prefix' or setting == 'AnyPrefix' or setting == 'SpecialPrefix' then
									local orig = Settings[setting]
									for i,v in pairs(server.Commands) do
										if v.Prefix == orig then
											v.Prefix = value
										end
									end
								end

								Settings[setting] = value
							end
						end
					end

					if SavedTables then
						for i,tData in pairs(SavedTables) do
							if tData.TableName and tData.TableKey and not Blacklist[tData.tableName] then
								local data = GetData(tData.TableKey);
								if data then
									for k,v in ipairs(data) do
										LoadData("TableUpdate", v)
									end
								end
							elseif tData.Table and tData.Action then
								LoadData("TableUpdate", tData)
							end
						end

						if Core.Variables.TimeBans then
							for i,v in pairs(Core.Variables.TimeBans) do
								if v.EndTime-os.time() <= 0 then
									table.remove(Core.Variables.TimeBans, i)
									DoSave({
										Type = "TableRemove";
										Table = {"Core", "Variables", "TimeBans"};
										Value = v;
									})
								end
							end
						end
					end

					AddLog(Logs.Script,{
						Text = "Loaded saved data";
						Desc = "Data was retrieved from the datastore and loaded successfully";
					})
				end
			end
		end;

		StartAPI = function()
			local _G = _G
			local setmetatable = setmetatable
			local rawset = rawset
			local rawget = rawget
			local type = type
			local error = error
			local print = print
			local warn = warn
			local pairs = pairs
			local next = next
			local table = table
			local getfenv = getfenv
			local setfenv = setfenv
			local require = require
			local tostring = tostring
			local server = server
			local service = service
			local Routine = Routine
			local cPcall = cPcall
			local MetaFunc = service.MetaFunc
			local StartLoop = service.StartLoop
			local API_Special = {
				AddAdmin = Settings.Allowed_API_Calls.DataStore;
				RemoveAdmin = Settings.Allowed_API_Calls.DataStore;
				RunCommand = Settings.Allowed_API_Calls.Core;
				SaveTableAdd = Settings.Allowed_API_Calls.DataStore and Settings.Allowed_API_Calls.Settings;
				SaveTableRemove = Settings.Allowed_API_Calls.DataStore and Settings.Allowed_API_Calls.Settings;
				SaveSetSetting = Settings.Allowed_API_Calls.DataStore and Settings.Allowed_API_Calls.Settings;
				ClearSavedSettings = Settings.Allowed_API_Calls.DataStore and Settings.Allowed_API_Calls.Settings;
				SetSetting = Settings.Allowed_API_Calls.Settings;
			}

			setfenv(1,setmetatable({}, {__metatable = getmetatable(getfenv())}))

			local API_Specific = {
				API_Specific = {
					Test = function()
						print("We ran the api specific stuff")
					end
				};
				Settings = Settings;
				Service = service;
			}

			local API = {
				Access = MetaFunc(function(...)
					local args = {...}
					local key = args[1]
					local ind = args[2]
					local targ

					if API_Specific[ind] then
						targ = API_Specific[ind]
					elseif server[ind] and Settings.Allowed_API_Calls[ind] then
						targ = server[ind]
					end

					if Settings.G_Access and key == Settings.G_Access_Key and targ and Settings.Allowed_API_Calls[ind] == true then
						if type(targ) == "table" then
							return service.NewProxy {
								__index = function(tab,inde)
									if targ[inde] ~= nil and API_Special[inde] == nil or API_Special[inde] == true then
										AddLog(Logs.Script,{
											Text = "Access to "..tostring(inde).." was granted";
											Desc = "A server script was granted access to "..tostring(inde);
										})

										if targ[inde]~=nil and type(targ[inde]) == "table" and Settings.G_Access_Perms == "Read" then
											return service.ReadOnly(targ[inde])
										else
											return targ[inde]
										end
									elseif API_Special[inde] == false then
										AddLog(Logs.Script,{
											Text = "Access to "..tostring(inde).." was denied";
											Desc = "A server script attempted to access "..tostring(inde).." via _G.Adonis.Access";
										})

										error("Access Denied: "..tostring(inde))
									else
										error("Could not find "..tostring(inde))
									end
								end;
								__newindex = function(tabl,inde,valu)
									if Settings.G_Access_Perms == "Read" then
										error("Read-only")
									elseif Settings.G_Access_Perms == "Write" then
										tabl[inde] = valu
									end
								end;
								__metatable = true;
							}
						end
					else
						error("Incorrect key or G_Access is disabled")
					end
				end);

				Scripts = service.ReadOnly({
					ExecutePermission = MetaFunc(function(srcScript, code)
						local exists;

						for i,v in pairs(Core.ScriptCache) do
							if v.Script == srcScript then
								exists = v
							end
						end

						if exists and exists.noCache ~= true and (not exists.runLimit or (exists.runLimit and exists.Executions <= exists.runLimit)) then
							exists.Executions = exists.Executions+1
							return exists.Source, exists.Loadstring
						end

						local data = Core.ExecutePermission(srcScript, code)
						if data and data.Source then
							local module;
							if not exists then
								module = require(server.Shared.FiOne:Clone())
								table.insert(Core.ScriptCache,{
									Script = srcScript;
									Source = data.Source;
									Loadstring = module;
									noCache = data.noCache;
									runLimit = data.runLimit;
									Executions = data.Executions;
								})
							else
								module = exists.Loadstring
								exists.Source = data.Source
							end
							return data.Source, module
						end
					end);
				}, nil, nil, true);

				CheckAdmin = MetaFunc(Admin.CheckAdmin);

				IsAdmin = MetaFunc(Admin.CheckAdmin);

				IsBanned = MetaFunc(Admin.CheckBan);

				IsMuted = MetaFunc(Admin.IsMuted);

				CheckDonor = MetaFunc(Admin.CheckDonor);

				GetLevel = MetaFunc(Admin.GetLevel);

				SetLighting = MetaFunc(Functions.SetLighting);

				SetPlayerLighting = MetaFunc(Remote.SetLighting);

				NewParticle = MetaFunc(Functions.NewParticle);

				RemoveParticle = MetaFunc(Functions.RemoveParticle);

				NewLocal = MetaFunc(Remote.NewLocal);

				MakeLocal = MetaFunc(Remote.MakeLocal);

				MoveLocal = MetaFunc(Remote.MoveLocal);

				RemoveLocal = MetaFunc(Remote.RemoveLocal);

				Hint = MetaFunc(Functions.Hint);

				Message = MetaFunc(Functions.Message);

				RunCommandAsNonAdmin = MetaFunc(Admin.RunCommandAsNonAdmin);
			}

			local AdonisGTable = service.NewProxy({
				__index = function(tab,ind)
					if Settings.G_API then
						return API[ind]
					elseif ind == "Scripts" then
						return API.Scripts
					else
						error("_G API is disabled")
					end
				end;
				__newindex = function()
					error("Read-only")
				end;
				__metatable = true;
			})

			if not rawget(_G, "Adonis") then
				if table.isfrozen and not table.isfrozen(_G) or not table.isfrozen then
					rawset(_G, "Adonis", AdonisGTable)
					StartLoop("APICheck", 1, function()
						if rawget(_G, "Adonis") ~= AdonisGTable then
							if table.isfrozen and not table.isfrozen(_G) or not table.isfrozen then
								rawset(_G, "Adonis", AdonisGTable)
							else
								warn("ADONIS CRITICAL WARNING! MALICIOUS CODE IS TRYING TO CHANGE THE ADONIS _G API AND IT CAN'T BE SET BACK! PLEASE SHUTDOWN THE SERVER AND REMOVE THE MALICIOUS CODE IF POSSIBLE!")
							end
						end
					end, true)
				else
					warn("The _G table was locked and the Adonis _G API could not be loaded")
				end
			end


			AddLog(Logs.Script,{
				Text = "Started _G API";
				Desc = "_G API was initialized and is ready to use";
			})
		end;
	};
end