From ab3463fa160f9996d29df5a3ebc73a006ae89930 Mon Sep 17 00:00:00 2001 From: beu Date: Wed, 18 Dec 2024 16:57:45 +0100 Subject: [PATCH] add main logic --- Classes/InterfacesConfig.as | 37 +++++++ Classes/LayerConfig.as | 51 +++++++++ InterfacesManager.as | 210 ++++++++++++++++++++++++++++++++++++ RenderManager.as | 70 ++++++++++++ Settings.as | 3 + main.as | 144 ++----------------------- 6 files changed, 378 insertions(+), 137 deletions(-) create mode 100644 Classes/InterfacesConfig.as create mode 100644 Classes/LayerConfig.as create mode 100644 InterfacesManager.as create mode 100644 RenderManager.as create mode 100644 Settings.as diff --git a/Classes/InterfacesConfig.as b/Classes/InterfacesConfig.as new file mode 100644 index 0000000..24a5855 --- /dev/null +++ b/Classes/InterfacesConfig.as @@ -0,0 +1,37 @@ +class InterfacesConfig { + string Name; + string Author; + string Version; + string ModePattern; + array Layers; + array UnloadModules; + + InterfacesConfig() {} + + InterfacesConfig(const string &in _ParentPath, const string &in _Json) { + const Json::Value@ Data = Json::Parse(_Json); + + Name = Data['Name']; + Author = Data['Author']; + Version = Data['Version']; + ModePattern = Data['ModePattern']; + + if (Data.HasKey('Layers') && Data['Layers'].GetType() == Json::Type::Array) { + for (uint i = 0; i < Data['Layers'].Length; i++) { + try { + const LayerConfig LayerConfig(_ParentPath, Data['Layers'][i]); + + Layers.InsertLast(LayerConfig); + } catch { + warn("Can't load layer \""+ i +"\" of Interface Config: \""+ Name +"\""); + } + } + } + + if (Data.HasKey('UnloadModules') && Data['UnloadModules'].GetType() == Json::Type::Array) { + for (uint i = 0; i < Data['UnloadModules'].Length; i++) { + UnloadModules.InsertLast(Data['UnloadModules'][i]); + } + } + } +} \ No newline at end of file diff --git a/Classes/LayerConfig.as b/Classes/LayerConfig.as new file mode 100644 index 0000000..0d9215f --- /dev/null +++ b/Classes/LayerConfig.as @@ -0,0 +1,51 @@ +class LayerConfig { + string Name; + string Path; + + CGameUILayer::EUILayerType LayerType = CGameUILayer::EUILayerType::Normal; + string AttachId = ''; + + LayerConfig() {} + + LayerConfig(const string &in _ParentPath, const Json::Value &in _Data) { + Path = Path::Join(_ParentPath, _Data['Path']); + + const string Manialink = GetManialink(); + Name = InterfacesManager::GetLayerName(Manialink); + + if (_Data.HasKey('LayerType') && _Data['LayerType'].GetType() == Json::Type::String) { + LayerType = CastEUILayerType(_Data['LayerType']); + } + + if (_Data.HasKey('AttachId') && _Data['AttachId'].GetType() == Json::Type::String) { + AttachId = _Data['AttachId']; + } + } + + string GetManialink() const { + if (!IO::FileExists(Path)) { + throw('Invalid Layer: Can\'t find "' + Path + '"'); + } + + IO::File File(Path, IO::FileMode::Read); + const string Manialink = File.ReadToEnd(); + return Manialink; + } + + CGameUILayer::EUILayerType CastEUILayerType(const string &in _LayerType) { + if (_LayerType == 'Normal') return CGameUILayer::EUILayerType::Normal; + if (_LayerType == 'ScoresTable') return CGameUILayer::EUILayerType::ScoresTable; + if (_LayerType == 'ScreenIn3d') return CGameUILayer::EUILayerType::ScreenIn3d; + if (_LayerType == 'AltMenu') return CGameUILayer::EUILayerType::AltMenu; + if (_LayerType == 'Markers') return CGameUILayer::EUILayerType::Markers; + if (_LayerType == 'CutScene') return CGameUILayer::EUILayerType::CutScene; + if (_LayerType == 'InGameMenu') return CGameUILayer::EUILayerType::InGameMenu; + if (_LayerType == 'EditorPlugin') return CGameUILayer::EUILayerType::EditorPlugin; + if (_LayerType == 'ManiaplanetPlugin') return CGameUILayer::EUILayerType::ManiaplanetPlugin; + if (_LayerType == 'ManiaplanetMenu') return CGameUILayer::EUILayerType::ManiaplanetMenu; + if (_LayerType == 'LoadingScreen') return CGameUILayer::EUILayerType::LoadingScreen; + + throw('Invalid EUILayerType: '+ _LayerType); + return CGameUILayer::EUILayerType::Normal; + } +} \ No newline at end of file diff --git a/InterfacesManager.as b/InterfacesManager.as new file mode 100644 index 0000000..599285b --- /dev/null +++ b/InterfacesManager.as @@ -0,0 +1,210 @@ +namespace InterfacesManager { + uint G_UILayersCount; + dictionary G_Configs; + array G_EnabledConfigs; + + void LoadConfigs() { + G_Configs = {}; + + const string Root = IO::FromStorageFolder(''); + + const array@ Folders = IO::IndexFolder(Root, false); + for (uint i = 0; i < Folders.Length; i++) { + const string Path = Folders[i]; + + // Get Id + string Id; + const array Parts = Path.Split('/'); + for (uint j = Parts.Length - 1; j >= 0; j--) { + if (Parts[j].Length > 0) { + Id = Parts[j]; + break; + } + } + + if (IO::FileExists(Path)) { + trace('Invalid Interface Config: "' + Path + '" is a file'); + continue; + } + + const string InfoFile = Path::Join(Path, 'info.json'); + if (!IO::FileExists(InfoFile)) { + trace('Invalid Interface Config: "' + Path + '" has no info.json file'); + continue; + } + + IO::File File(InfoFile, IO::FileMode::Read); + + if (File.Size() <= 0) { + trace('Invalid Interface Config: "' + Path + '" is not readable'); + continue; + } + + const string FileContent = File.ReadToEnd(); + + try { + const InterfacesConfig Config(Path, FileContent); + G_Configs[Id] = Config; + } catch { + warn('Invalid Interface Config: "' + Path + '" is an invalid json'); + } + } + + if (S_EnabledConfigs.Length > 0) { + G_EnabledConfigs = S_EnabledConfigs.Split(','); + } + + for (uint i = 0; i < G_EnabledConfigs.Length; i++) { + const string Id = G_EnabledConfigs[i]; + if (G_Configs.Exists(Id)) { + continue; + } + + RenderManager::NotifyWarning("Enabled interfaces " + Id + " has been deleted."); + G_EnabledConfigs.RemoveAt(i); + } + } + + void InjectInterfacesIfNeeded() { + CTrackManiaNetwork@ Network = cast(GetApp().Network); + CGameManiaAppPlayground@ ManiaApp = Network.ClientManiaAppPlayground; + CTrackManiaNetworkServerInfo@ ServerInfo = cast(Network.ServerInfo); + + if (ManiaApp !is null) { + MwFastBuffer UILayers = ManiaApp.UILayers; + + dictionary IndexedLayers = GetIndexedUILayers(UILayers); + + for (uint i = 0; i < G_EnabledConfigs.Length; i++) { + const string Id = G_EnabledConfigs[i]; + + const InterfacesConfig@ Config = InterfacesManager::GetConfig(Id); + + // TODO Check ModePattern + if (Regex::IsMatch(ServerInfo.CurScriptRelName, Config.ModePattern)) { + print('match'); + } + + for (uint j = 0; j < Config.UnloadModules.Length; j++) { + const string ModuleId = Config.UnloadModules[j]; + + if (IndexedLayers.Exists(ModuleId)) { + print('Removing UI Layer: "' + ModuleId + '"'); + CGameUILayer@ Layer = cast(IndexedLayers[ModuleId]); + + /** + * Some UILayer can have Maniascript that can interact with the game, even hidden. + * So it's safer to destroy them. + */ + ManiaApp.UILayerDestroy(Layer); + } + } + + for (uint j = 0; j < Config.Layers.Length; j++) { + const LayerConfig@ LayerConfig = Config.Layers[j]; + + CGameUILayer@ Layer; + if (IndexedLayers.Exists(LayerConfig.Name)) { + print('Updating Layer "'+ LayerConfig.Name +'"'); + @Layer = cast(IndexedLayers[LayerConfig.Name]); + } else { + print('Creating new Layer "'+ LayerConfig.Name +'"'); + @Layer = ManiaApp.UILayerCreate(); + } + + const string Manialink = LayerConfig.GetManialink(); + Layer.ManialinkPage = Manialink; + Layer.AttachId = LayerConfig.AttachId; + Layer.Type = LayerConfig.LayerType; + } + } + G_UILayersCount = UILayers.Length; + } + } + + void EnableInterface(const string &in _Id) { + trace('Enabling Interface: '+ _Id); + + const int Index = G_EnabledConfigs.Find(_Id); + if (Index >= 0) { + trace("Can't disable config \""+ _Id +"\": already enabled"); + return; + } + + G_EnabledConfigs.InsertLast(_Id); + S_EnabledConfigs = string::Join(G_EnabledConfigs, ','); + + InjectInterfacesIfNeeded(); + } + + void DisableInterface(const string &in _Id) { + trace('Disabling Interface: '+ _Id); + + const int Index = G_EnabledConfigs.Find(_Id); + if (Index < 0) { + trace("Can't disable config \""+ _Id +"\": not enabled"); + return; + } + G_EnabledConfigs.RemoveAt(Index); + + S_EnabledConfigs = string::Join(G_EnabledConfigs, ','); + } + + InterfacesConfig@ GetConfig(const string &in _Id) { + if (InterfacesManager::G_Configs.Exists(_Id)) { + return cast(InterfacesManager::G_Configs[_Id]); + } + return null; + } + + dictionary GetIndexedUILayers(MwFastBuffer _UILayers) { + dictionary IndexedLayers; + + for (uint i = 0; i < _UILayers.Length; i++) { + CGameUILayer@ UILayer = _UILayers[i]; + + const string Name = GetLayerName(UILayer.ManialinkPage); + if (Name.Length > 0) { + IndexedLayers[Name] = @UILayer; + } + } + return IndexedLayers; + } + + string GetLayerName(const string &in _Manialink) { + array FirstLines = _Manialink.Split("\n", 5); + + if (FirstLines.Length > 0) { + for (uint j = 0; j < FirstLines.Length - 1; j++) { + if (FirstLines[j].Contains('(GetApp().Network); + + if (Network.ClientManiaAppPlayground !is null) { + CGameManiaAppPlayground @ManiaApp = Network.ClientManiaAppPlayground; + MwFastBuffer UILayers = ManiaApp.UILayers; + + /** + * Gamemode can inject UILayer at any time + * IMO, it's better to check count instead of gamemode name or anything else + */ + if (G_UILayersCount != UILayers.Length) { + G_UILayersCount = UILayers.Length; + InjectInterfacesIfNeeded(); + } + } else { + G_UILayersCount = 0; + } + } +} \ No newline at end of file diff --git a/RenderManager.as b/RenderManager.as new file mode 100644 index 0000000..2739409 --- /dev/null +++ b/RenderManager.as @@ -0,0 +1,70 @@ +namespace RenderManager { + bool G_RenderInterface = Meta::IsDeveloperMode(); // render by default in dev mode + + void RenderInterface() { + if (G_RenderInterface) { + UI::Begin(Icons::Television + " Custom Interface Loader", G_RenderInterface); + + if (UI::Button('Open Folder')) { + OpenExplorerPath(IO::FromStorageFolder('')); + } + + UI::SameLine(); + + if (UI::Button('Reload')) { + InterfacesManager::LoadConfigs(); + } + + if (UI::BeginTable('configs', 5, UI::TableFlags(UI::TableFlags::Resizable | UI::TableFlags::Sortable | UI::TableFlags::NoSavedSettings | UI::TableFlags::BordersInnerV | UI::TableFlags::SizingStretchProp | UI::TableFlags::ScrollY))) { + UI::TableSetupColumn("Name", UI::TableColumnFlags::DefaultSort); + UI::TableSetupColumn("Author", UI::TableColumnFlags::None); + UI::TableSetupColumn("Version", UI::TableColumnFlags::None); + UI::TableSetupColumn("Mode Pattern", UI::TableColumnFlags::NoResize); + UI::TableSetupColumn("Enable", UI::TableFlags(UI::TableColumnFlags::NoResize | UI::TableColumnFlags::WidthFixed), 60.); + UI::TableHeadersRow(); + + const array ConfigIds = InterfacesManager::G_Configs.GetKeys(); + + for (uint i = 0; i < ConfigIds.Length; i++) { + const string Id = ConfigIds[i]; + const InterfacesConfig@ Config = InterfacesManager::GetConfig(Id); + + UI::TableNextRow(); + UI::TableNextColumn(); + UI::Text(Config.Name); + UI::TableNextColumn(); + UI::Text(Config.Author); + UI::TableNextColumn(); + UI::Text(Config.Version); + UI::TableNextColumn(); + UI::Text(Config.ModePattern); + UI::TableNextColumn(); + + const int index = InterfacesManager::G_EnabledConfigs.Find(Id); + const bool enabled = (index >= 0); + const bool newValue = UI::Checkbox('###'+ Id, enabled); + if (newValue != enabled) { + if (enabled) { + InterfacesManager::DisableInterface(Id); + } else { + InterfacesManager::EnableInterface(Id); + } + } + } + UI::EndTable(); + } + + UI::End(); + } + } + + void RenderMenu() { + if (UI::MenuItem(Icons::Television + ' Custom Interface Loader', "", G_RenderInterface)) { + G_RenderInterface = !G_RenderInterface; + } + } + + void NotifyWarning(const string &in _Message) { + UI::ShowNotification('Custom Interfaces Loader', _Message, vec4(0.9, 0.7, 0.2, 1.), 5000); + } +} \ No newline at end of file diff --git a/Settings.as b/Settings.as new file mode 100644 index 0000000..f685e57 --- /dev/null +++ b/Settings.as @@ -0,0 +1,3 @@ + +[Setting hidden] +string S_EnabledConfigs = ''; \ No newline at end of file diff --git a/main.as b/main.as index 507dd27..b722550 100755 --- a/main.as +++ b/main.as @@ -1,144 +1,14 @@ -const array C_ModuleToDisable = { - "UIModule_ChampionSpring2022_MatchInfo", - "UIModule_ChampionSpring2022_ScoresHeader", - "UIModule_ChampionSpring2022_Sponsors", - "UIModule_ChampionCup_LiveRanking", - "UIModule_ChampionCup_SpectatorInfo", - "UIModule_ChampionCup_Chat", - "UIModule_ChampionCup_MapInfo", - "UIModule_ChampionCup_Countdown", - "UIModule_Race_ScoresTable", - "UIModule_ChampionCup_Sign16x9_1", - "UIModule_ChampionCup_Sign16x9_2", - "UIModule_ChampionCup_Sign16x9_3", - "UIModule_ChampionCup_Sign16x9_4", - "UIModule_ChampionCup_Sign16x9_5", - "UIModule_ChampionCup_Sign64x10_64x10_Start", - "UIModule_ChampionCup_Sign64x10_64x10_Finish", - "UIModule_ChampionCup_Sign64x10_64x10_Checkpoint", - "UIModule_ChampionCup_Sign2x3_1" -}; - void Main() { - auto Network = cast(GetApp().Network); + InterfacesManager::LoadConfigs(); - /** - * Unload already loaded custom UI - */ - if (Network.ClientManiaAppPlayground !is null) { - CGameManiaAppPlayground @ManiaApp = Network.ClientManiaAppPlayground; - array Manialinks = GetManialinkFiles(); - - array CustomInterfacesIds; - - for (uint i = 0; i < Manialinks.Length; i++) { - CustomInterfacesIds.InsertLast(Path::GetFileNameWithoutExtension(Manialinks[i])); - } - - MwFastBuffer UILayers = ManiaApp.UILayers; - - for (uint i = 0; i < UILayers.Length; i++) { - CGameUILayer @UILayer = UILayers[i]; - string ManialinkPage = UILayer.ManialinkPage; - array FirstLines = ManialinkPage.Split("\n", 5); - if (FirstLines.Length > 0) { - for (uint j = 0; j < FirstLines.Length - 1; j++) { - if (FirstLines[j].Contains(' -1) { - print("Updating existing layer " + LayerId); - UILayer.ManialinkPage = GetManialinkPage(LayerId); - } else { - print("Destroying not existing layer " + LayerId); - ManiaApp.UILayerDestroy(UILayer); - } - } - } - } - } - } - } + while (true) { + yield(); + InterfacesManager::Yield(); } +} - uint count = 0; - - while(true) { - yield(); - if (Network.ClientManiaAppPlayground !is null) { - CGameManiaAppPlayground @ManiaApp = Network.ClientManiaAppPlayground; - MwFastBuffer UILayers = ManiaApp.UILayers; - - if (count != UILayers.Length) { - print('Walking through UILayers'); - - array CustomUIModulesLoaded = {}; - - for (uint i = 0; i < UILayers.Length; i++) { - CGameUILayer @UILayer = UILayers[i]; - string ManialinkPage = UILayer.ManialinkPage; - array FirstLines = ManialinkPage.Split("\n", 5); - if (FirstLines.Length > 0) { - for (uint j = 0; j < FirstLines.Length - 1; j++) { - if (FirstLines[j].Contains(' -1) { - print('Deleting ' + id); - ManiaApp.UILayerDestroy(UILayers[i]); - } - - if (id.StartsWith('Custom_TMWC2024_')) { - CustomUIModulesLoaded.InsertLast(id); - } - } - } - } - } - } - - array Manialinks = GetManialinkFiles(); - - for (uint i = 0; i < Manialinks.Length; i++) { - string FilePath = Manialinks[i]; - string FileName = Path::GetFileName(FilePath); - if (FileName.EndsWith('.xml')) { - string LayerId = Path::GetFileNameWithoutExtension(FileName); - if (CustomUIModulesLoaded.Find(LayerId) == -1) { - print("Loading layer " + LayerId); - CGameUILayer @UILayer = ManiaApp.UILayerCreate(); - UILayer.ManialinkPage = GetManialinkPage(LayerId); - - if (LayerId.Contains("_Sign16x9Small") || LayerId.Contains("_Sign155Small")) { - UILayer.Type = CGameUILayer::EUILayerType::ScreenIn3d; - UILayer.AttachId = "155_StadiumSmall"; - } else if (LayerId.Contains("_Sign16x9") || LayerId.Contains("_Sign155")) { - UILayer.Type = CGameUILayer::EUILayerType::ScreenIn3d; - UILayer.AttachId = "155_Stadium"; - } else if (LayerId.Contains("_Sign2x3")) { - UILayer.Type = CGameUILayer::EUILayerType::ScreenIn3d; - UILayer.AttachId = "2x3_Stadium"; - } else if (LayerId.Contains("_Markers")) { - UILayer.Type = CGameUILayer::EUILayerType::Markers; - } - } - } - } - - count = ManiaApp.UILayers.Length; - } - } else { - count = 0; - } - } +void RenderInterface() { + RenderManager::RenderInterface(); } array GetManialinkFiles() {