예제 #1
0
        /// <summary>
        /// Initializes standard components for this helper
        /// </summary>
        /// <param name="patchToRun"></param>
        /// <param name="filePath"></param>
        /// <param name="currentCulture"></param>
        /// <param name="log">Log to write events to (optional)</param>
        private static void _InitializeComponents(PCH patchToRun, string filePath, DB.Culture currentCulture, Log log)
        {
            // Testing TDU main folder
            _TestTduInstallPath();

            // Messages
            _Messages.Clear();

            // Current patch
            _CurrentPatch = patchToRun;

            // Current path
            // BUG_64: using specified folder as startup directory
            _CurrentPath = filePath;

            // Culture
            _CurrentCulture = currentCulture;

            // Logger
            if (log == null)
            {
                _PatchLog = new Log(null);
            }
            else
            {
                _PatchLog = log;
            }
        }
예제 #2
0
        /// <summary>
        /// Main constructor
        /// </summary>
        /// <param name="currentPatch">Patch to display properties</param>
        public PatchPropertiesDialog(PCH currentPatch)
        {
            InitializeComponent();
            _CurrentPatch = currentPatch;

            // Dependancies and checked states
            foreach (PCH.InstallGroup anotherGroup in _CurrentPatch.Groups)
            {
                string currentGroupName = anotherGroup.name;

                // Dependencies
                if (anotherGroup.parentName != null)
                {
                    _CurrentDependencies.Add(currentGroupName, anotherGroup.parentName);
                }

                // Prechecked state
                _CurrentCheckedStates.Add(currentGroupName, anotherGroup.isDefaultChecked);

                // Exclusions
                if (anotherGroup.excludedGroupNames == null)
                {
                    _CurrentExclusions.Add(currentGroupName, new Collection <string>());
                }
                else
                {
                    _CurrentExclusions.Add(currentGroupName, anotherGroup.excludedGroupNames);
                }
            }

            _InitializeContents();
        }
예제 #3
0
        /// <summary>
        /// Loads patch file data from specified file
        /// </summary>
        /// <param name="installPatchFile">Install patch file to load</param>
        /// <param name="uninstallPatchFile">Uninstall patch file to load (optional - can be null)</param>
        private void _LoadData(string installPatchFile, string uninstallPatchFile)
        {
            // Install file loading
            PCH currentPatch = TduFile.GetFile(installPatchFile) as PCH;

            if (currentPatch == null)
            {
                throw new Exception(_ERROR_INVALID_PATCH_FILE);
            }
            if (!currentPatch.Exists)
            {
                throw new FileNotFoundException("", installPatchFile);
            }

            _CurrentInstallPatch = currentPatch;

            // Uninstall file loading
            currentPatch = null;

            if (!string.IsNullOrEmpty(uninstallPatchFile))
            {
                currentPatch = TduFile.GetFile(uninstallPatchFile) as PCH;
            }

            if (currentPatch == null || !currentPatch.Exists)
            {
                Log.Info(_WARN_NO_UNINSTALL);
            }
            else
            {
                _CurrentUninstallPatch = currentPatch;
            }

            // Patch info
            // Name - version
            if (string.IsNullOrEmpty(_CurrentInstallPatch.Version))
            {
                titleLabel.Text = _CurrentInstallPatch.Name;
            }
            else
            {
                titleLabel.Text = string.Format(_FORMAT_LABEL_PROJECT, _CurrentInstallPatch.Name, _CurrentInstallPatch.Version);
            }

            // Author - date
            if (string.IsNullOrEmpty(_CurrentInstallPatch.Date))
            {
                contribLabel.Text = _CurrentInstallPatch.Author;
            }
            else
            {
                contribLabel.Text = string.Format(_FORMAT_LABEL_CONTRIBUTOR, _CurrentInstallPatch.Author, _CurrentInstallPatch.Date);
            }

            // EVO_142 : Free label
            freeLabel.Text = _CurrentInstallPatch.Free;
        }
예제 #4
0
        /// <summary>
        /// Main constructor
        /// </summary>
        /// <param name="currentPatch"></param>
        public GroupsDialog(PCH currentPatch)
        {
            InitializeComponent();

            if (currentPatch != null)
            {
                _Patch = currentPatch;

                _InitializeContents();
            }
        }
예제 #5
0
        /// <summary>
        /// Main constructor
        /// </summary>
        /// <param name="patch"></param>
        /// <param name="customLabel"></param>
        public PatchInstructionsDialog(PCH patch, string customLabel)
        {
            InitializeComponent();

            if (patch == null)
            {
                throw new Exception(_ERROR_PATCH_MISSING);
            }

            _CurrentPatch = patch;
            _CustomLabel  = customLabel;
            _InitializeContents();
        }
예제 #6
0
        /// <summary>
        /// Handles single execution
        /// </summary>
        /// <param name="patchToRun">Patch instance to execute</param>
        /// <param name="instructionNumber">Instruction number to execute</param>
        /// <param name="log">Log for message storing</param>
        /// <param name="tduPath">Path for tdu files; can be null = default</param>
        /// <param name="filePath">Path for patch files</param>
        /// <param name="currentCulture"></param>
        /// <returns></returns>
        public static RunResult RunSingleInstruction(PCH patchToRun, int instructionNumber, Log log, string tduPath, string filePath, DB.Culture currentCulture)
        {
            RunResult result = RunResult.NotRun;

            if (patchToRun == null || string.IsNullOrEmpty(filePath))
            {
                return(result);
            }

            _InitializeComponents(patchToRun, filePath, currentCulture, log);

            // Instruction finding
            PatchInstruction instr = patchToRun.GetInstruction(instructionNumber);

            if (instr != null)
            {
                result = _ProcessInstruction(instr, instructionNumber);

                switch (result)
                {
                case RunResult.RunWithWarnings:
                    // Non-critical failure > next instruction
                    _PatchLog.WriteEvent(string.Format(_MSG_INSTRUCTION_END_WARN, instr.Order, 1));
                    break;

                case RunResult.RunWithErrors:
                    _PatchLog.WriteEvent(_MSG_FINISHING_ERR);
                    break;

                default:
                    _PatchLog.WriteEvent(_MSG_FINISHING_OK);
                    break;
                }
            }

            // Displaying collected messages
            _DisplayCollectedMessages();

            // Saving Logger to file
            _PatchLog.SaveToFile(_PatchLog.Name);

            return(result);
        }
        private string SourceFilePart()
        {
            if (PCH == null || PCH.Length == 0 || PCH == "none")
            {
                return(FilePart(CFiles));
            }
            int    lpos     = PCH.LastIndexOf('.');
            string pcc      = PCH.Substring(0, lpos) + ".cpp";
            string pcclower = pcc.ToLower();

            string rtn = "";

            string[] files = CFiles.Split(new char[1] {
                ';'
            });
            foreach (string f in files)
            {
                if (f.Length > 0)
                {
                    if (f.ToLower() == pcclower)
                    {
                        rtn +=
                            "  <File\r\n" +
                            "    RelativePath=\"" + pcc + "\"\r\n" +
                            "  >\r\n" +
                            PCHSourceFileConfigurationPart("Debug") +
                            PCHSourceFileConfigurationPart("Release") +
                            ((NoOpt == "Release") ? PCHSourceFileConfigurationPart("Release_NoOptForParser") : "") +
                            ((NoOpt == "Debug") ? PCHSourceFileConfigurationPart("Release_NoOptForParser") : "") +
                            "  </File>\r\n";
                    }
                    else
                    {
                        rtn += "  <File\r\n    RelativePath=\"" + f + "\"\r\n  >\r\n  </File>\r\n";
                    }
                }
            }
            return(rtn);
        }
예제 #8
0
파일: TduFile.cs 프로젝트: djey47/tdumt
        /// <summary>
        /// Returns the right TDUFile according to specified file.
        /// </summary>
        /// <param name="fileName">file name, without path</param>
        /// <returns>null if file is from an unsupported type</returns>
        public static TduFile GetFile(string fileName)
        {
            TduFile  tduFile = new Regular();
            FileInfo fi      = new FileInfo(fileName);

            // New mapping management

            // Cameras
            if (Regex.IsMatch(fileName, Cameras.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                if (fi.Exists)
                {
                    tduFile = new Cameras(fileName);
                }
                else
                {
                    tduFile = new Cameras();
                }
            }
            // AIConfig
            else if (Regex.IsMatch(fileName, AIConfig.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                if (fi.Exists)
                {
                    tduFile = new AIConfig(fileName);
                }
                else
                {
                    tduFile = new AIConfig();
                }
            }
            // DB
            else if (Regex.IsMatch(fileName, DB.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                if (fi.Exists)
                {
                    tduFile = new DB(fileName);
                }
                else
                {
                    tduFile = new DB();
                }
            }
            // BNK
            else if (Regex.IsMatch(fileName, BNK.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                tduFile = new BNK(fileName);
            }
            // DDS
            else if (Regex.IsMatch(fileName, DDS.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                if (fi.Exists)
                {
                    tduFile = new DDS(fileName);
                }
                else
                {
                    tduFile = new DDS();
                }
            }
            // 2DB
            else if (Regex.IsMatch(fileName, _2DB.FILENAME_PATTERN, RegexOptions.IgnoreCase) ||
                     Regex.IsMatch(fileName, _2DB.FILENAME_OLD_PATTERN, RegexOptions.IgnoreCase))
            {
                if (fi.Exists)
                {
                    tduFile = new _2DB(fileName);
                }
                else
                {
                    tduFile = new _2DB();
                }
            }
            // MAP
            else if (Regex.IsMatch(fileName, MAP.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                if (fi.Exists)
                {
                    tduFile = new MAP(fileName);
                }
                else
                {
                    tduFile = new MAP();
                }
            }
            // XMB
            else if (Regex.IsMatch(fileName, XMB.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                if (fi.Exists)
                {
                    tduFile = new XMB(fileName);
                }
                else
                {
                    tduFile = new XMB();
                }
            }
            // WAV + XMB_WAV
            else if (Regex.IsMatch(fileName, XMB_WAV.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                try
                {
                    if (fi.Exists)
                    {
                        tduFile = new XMB_WAV(fileName);
                    }
                    else
                    {
                        tduFile = new XMB_WAV();
                    }
                }
                catch (FormatException)
                {
                    // standard WAV file
                }
            }
            // PCH
            else if (Regex.IsMatch(fileName, PCH.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                tduFile = new PCH(fileName);
            }
            // DB Resources
            else if (Regex.IsMatch(fileName, DBResource.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                if (fi.Exists)
                {
                    tduFile = new DBResource(fileName);
                }
                else
                {
                    tduFile = new DBResource();
                }
            }
            // DFE
            else if (Regex.IsMatch(fileName, DFE.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                tduFile = new DFE(fileName);
            }
            // IGE
            else if (Regex.IsMatch(fileName, IGE.FILENAME_PATTERN, RegexOptions.IgnoreCase))
            {
                tduFile = new IGE(fileName);
            }
            // Regular by default
            else
            {
                tduFile = new Regular();
            }

            // To update common information
            tduFile._FinalizeLoading(fileName);

            return(tduFile);
        }
예제 #9
0
        /// <summary>
        /// Runs all install tasks. Takes chosen install groups into account
        /// </summary>
        /// <param name="patch"></param>
        /// <param name="log"></param>
        /// <param name="culture"></param>
        /// <param name="isInstall"></param>
        /// <param name="chosenInstallGroups"></param>
        public static void RunAll(PCH patch, Log log, DB.Culture culture, bool isInstall, List <string> chosenInstallGroups)
        {
            try
            {
                // 1. Preparatory : folders
                try
                {
                    // Setting transversal values
                    if (patch == null)
                    {
                        throw new Exception("No patch specified.");
                    }

                    _CurrentPatch = patch;

                    if (log != null)
                    {
                        _Log = log;
                    }

                    if (string.IsNullOrEmpty(Tools.TduPath))
                    {
                        throw new Exception("Invalid TDU install path specified.");
                    }

                    _Culture       = culture;
                    _InstallGroups = chosenInstallGroups;

                    // Go!
                    PreparatoryTask.Run();
                }
                catch (Exception ex)
                {
                    Log.Error("Install error when executing preparatory task.", ex);
                    throw;
                }

                // 2. Running patch
                try
                {
                    PatchTask.Run();
                }
                catch (Exception ex)
                {
                    Log.Error("Error when running core patch instructions.", ex);
                    throw;
                }

                // 3. Finalization
                try
                {
                    FinalizationTask.Run(isInstall);
                }
                catch (Exception ex)
                {
                    Log.Error("Error when executing finalization task.", ex);
                    throw;
                }
            }
            catch (Exception ex)
            {
                if (isInstall)
                {
                    throw new Exception("Current patch can't be installed:\r\n" + ex.Message, ex);
                }

                throw new Exception("Current patch can't be uninstalled:\r\n" + ex.Message, ex);
            }
        }
예제 #10
0
 /// <summary>
 /// Runs all install tasks
 /// </summary>
 /// <param name="patch"></param>
 /// <param name="patchLog"></param>
 /// <param name="culture"></param>
 /// <param name="isInstall"></param>
 public static void RunAll(PCH patch, Log patchLog, DB.Culture culture, bool isInstall)
 {
     RunAll(patch, patchLog, culture, isInstall, null);
 }
예제 #11
0
        /// <summary>
        /// Utility method converting specified installer patch to uninstaller one
        /// </summary>
        /// <param name="installerPatch"></param>
        /// <param name="uninstallerPatchFileName"></param>
        public static void ConvertInstallerToUninstaller(PCH installerPatch, string uninstallerPatchFileName)
        {
            if (installerPatch == null)
            {
                throw new Exception("Invalid installer patch specified.");
            }

            PCH uninstallerPatch = TduFile.GetFile(uninstallerPatchFileName) as PCH;

            if (uninstallerPatch == null)
            {
                throw new Exception("Unable to create specified uninstaller patch.");
            }

            // Cloning properties
            installerPatch.DuplicateProperties(uninstallerPatch);

            // Browsing instructions
            int ignoredCount = 0;

            foreach (PatchInstruction patchInstruction in installerPatch.PatchInstructions)
            {
                PatchInstruction correspondingInstruction = patchInstruction.Clone() as PatchInstruction;

                // checkPatch instruction is not used in uninstaller
                if (correspondingInstruction == null ||
                    PatchInstruction.InstructionName.checkPatch.ToString().Equals(correspondingInstruction.Name) ||
                    PatchInstruction.InstructionName.nothing.ToString().Equals(correspondingInstruction.Name))
                {
                    ignoredCount++;
                    continue;
                }

                // installFile instruction must be converted into uninstallFile, keeping parameter values
                if (PatchInstruction.InstructionName.installFile.ToString().Equals(correspondingInstruction.Name))
                {
                    correspondingInstruction =
                        PatchInstruction.ChangeInstruction(correspondingInstruction,
                                                           PatchInstruction.InstructionName.uninstallFile);
                }

                // bnkRemap instruction must be converted into bnkUnmap, keeping parameter values
                if (PatchInstruction.InstructionName.bnkRemap.ToString().Equals(correspondingInstruction.Name))
                {
                    correspondingInstruction =
                        PatchInstruction.ChangeInstruction(correspondingInstruction,
                                                           PatchInstruction.InstructionName.bnkUnmap);
                }

                // installPackedFile instruction must be converted into uninstallPackedFile, keeping parameter values
                if (PatchInstruction.InstructionName.installPackedFile.ToString().Equals(correspondingInstruction.Name))
                {
                    correspondingInstruction =
                        PatchInstruction.ChangeInstruction(correspondingInstruction,
                                                           PatchInstruction.InstructionName.uninstallPackedFile);
                }

                // Updates instruction index
                patchInstruction.Order -= ignoredCount;

                // Adding instruction to uninstaller
                uninstallerPatch.SetInstruction(correspondingInstruction);
            }

            // Saving
            uninstallerPatch.Save();
        }
예제 #12
0
        /// <summary>
        /// Handles patch execution
        /// </summary>
        /// <param name="patchToRun">Patch instance to execute</param>
        /// <param name="log">Log to store events - can be null</param>
        /// <param name="filePath">Path for patch files</param>
        /// <param name="currentCulture">Culture used in DB files</param>
        /// <param name="installGroups">List of instruction groups to install</param>
        /// <returns></returns>
        public static RunResult RunPatch(PCH patchToRun, Log log, string filePath, DB.Culture currentCulture, List <string> installGroups)
        {
            RunResult result           = RunResult.NotRun;
            int       warningCount     = 0;
            int       instructionCount = 1;

            if (patchToRun == null || string.IsNullOrEmpty(filePath))
            {
                return(result);
            }

            // Group handling
            if (installGroups == null)
            {
                installGroups = new List <string> {
                    PCH.REQUIRED_GROUP_NAME
                }
            }
            ;

            _InitializeComponents(patchToRun, filePath, currentCulture, log);

            _PatchLog.WriteEvent(string.Format(_MSG_RUNNING_PATCH, patchToRun.FileName, patchToRun.PatchInstructions.Count));
            _PatchLog.WriteEvent(string.Format(_MSG_CHOSEN_GROUPS, List2.ToString(installGroups)));

            // Instruction browsing
            bool isCritical = false;

            if (patchToRun.PatchInstructions.Count == 0)
            {
                _PatchLog.WriteEvent(_MSG_NO_INSTRUCTIONS);
            }
            else
            {
                foreach (PatchInstruction instr in patchToRun.PatchInstructions)
                {
                    if (instr.Enabled)
                    {
                        if (installGroups.Contains(instr.Group.name))
                        {
                            result = _ProcessInstruction(instr, instructionCount);

                            // Result analysis
                            switch (result)
                            {
                            case RunResult.RunWithErrors:
                                // Critical failure > patch stopped
                                _PatchLog.WriteEvent(string.Format(_MSG_INSTRUCTION_END_ERR, instr.Order));
                                isCritical = true;
                                break;

                            case RunResult.RunWithWarnings:
                                // Non-critical failure > next instruction
                                _PatchLog.WriteEvent(string.Format(_MSG_INSTRUCTION_END_WARN, instr.Order, 1));
                                warningCount++;
                                break;

                            default:
                                break;
                            }
                        }
                        else
                        {
                            // Refused instruction
                            _PatchLog.WriteEvent(string.Format(_MSG_INSTRUCTION_REFUSED, instr.Order, instr.Group.name));
                        }

                        if (isCritical)
                        {
                            break;
                        }
                    }
                    else
                    {
                        // Disabled instruction
                        _PatchLog.WriteEvent(string.Format(_MSG_INSTRUCTION_DISABLED, instr.Order));
                    }

                    // EVO_100: Progress update
                    if (_ProgressBar != null)
                    {
                        _ProgressBar.PerformStep();
                    }

                    if (_TSProgressBar != null)
                    {
                        _TSProgressBar.PerformStep();
                    }

                    Application.DoEvents();

                    instructionCount++;
                }
            }

            switch (result)
            {
            case RunResult.NotRun:
                _PatchLog.WriteEvent(_MSG_NOT_RUN);
                break;

            case RunResult.RunWithErrors:
                _PatchLog.WriteEvent(_MSG_FINISHING_ERR);
                break;

            case RunResult.OK:
                if (warningCount > 0)
                {
                    _PatchLog.WriteEvent(string.Format(_MSG_FINISHING_WAR, warningCount));
                    result = RunResult.RunWithWarnings;
                }
                else
                {
                    _PatchLog.WriteEvent(_MSG_FINISHING_OK);
                }
                break;

            default:
                break;
            }

            // Displaying collected messages
            _DisplayCollectedMessages();

            // Saving Logger to file
            _PatchLog.SaveToFile(_PatchLog.Name);

            return(result);
        }
예제 #13
0
        /// <summary>
        /// Manages component install/uninstall
        /// </summary>
        /// <param name="isInstall">true if install / false if uninstall</param>
        private void _Process(bool isInstall)
        {
            // Current culture
            //string selectedCulture = gameCultureComboBox.Text.Substring(0, 2);
            //DB.Culture currentCulture = (DB.Culture) Enum.Parse(typeof (DB.Culture), selectedCulture);
            // EVO_147
            const DB.Culture currentCulture = DB.Culture.US;

            // Selects right patch according to install/uninstall case
            PCH currentPatch = (isInstall ? _CurrentInstallPatch : _CurrentUninstallPatch);

            // TDU folder (override)
            Tools.TduPath = tduPathTextbox.Text;

            // Authorization checks
            // If specified ref matches a car pack vehicle, TDU version must be '1.66+megapack'
            if (Tools.InstalledTduVersion != Tools.TduVersion.V1_66_Megapack)
            {
                // Loading reference
                try
                {
                    VehicleSlotsHelper.InitReference(Application.StartupPath + LibraryConstants.FOLDER_XML);

                    // Getting vehicle information...
                    if (!Tools.KEY_MISC_SLOT.Equals(_CurrentInstallPatch.SlotRef) &&
                        VehicleSlotsHelper.VehicleInformation.ContainsKey(currentPatch.SlotRef))
                    {
                        VehicleSlotsHelper.VehicleInfo info =
                            VehicleSlotsHelper.VehicleInformation[currentPatch.SlotRef];

                        if (info.isAddon)
                        {
                            MessageBoxes.ShowWarning(this, _ERROR_MOD_NOT_ALLOWED);
                            return;
                        }
                    }
                }
                catch (Exception ex)
                {
                    MessageBoxes.ShowError(this, ex);
                    return;
                }
            }

            // If another mod is installed on current slot...
            // Exception for Patch 1.67d
            if (isInstall &&
                InstallHelper.IsAnotherModInstalled(currentPatch.SlotRef, currentPatch.Name) &&
                !AppConstants.SLOT_COMMUNITY_PATCH.Equals(currentPatch.SlotRef))
            {
                MessageBoxes.ShowWarning(this, string.Format(_ERROR_ANOTHER_MOD_INSTALLED, InstallHelper.GetInstalledModName(currentPatch.SlotRef)));
                return;
            }

            // EVO_134 Group handling, if necessary
            List <string> chosenInstallGroups = new List <string> {
                PCH.REQUIRED_GROUP_NAME
            };

            if (isInstall && currentPatch.Groups.Count > 1)
            {
                GroupsDialog dlg = new GroupsDialog(_CurrentInstallPatch);
                DialogResult dr  = dlg.ShowDialog(this);

                if (dr == DialogResult.OK)
                {
                    chosenInstallGroups = dlg.ChosenGroups;
                }
                else
                {
                    return;
                }
            }

            // Patch logger init
            string logFile  = Application.StartupPath + LibraryConstants.FILE_LOG_PATCH;
            Log    patchLog = new Log(logFile);

            patchLog.Appenders.Add(new ConsoleAppender());

            // EVO_100: progress bar init
            progressPanel.Visible = true;
            infoLabel.Visible     = false;

            mainProgressBar.Minimum = 0;
            mainProgressBar.Maximum = currentPatch.PatchInstructions.Count;
            mainProgressBar.Step    = 1;
            mainProgressBar.Value   = 0;
            PatchHelper.ProgressBar = mainProgressBar;

            // Using new helper
            try
            {
                InstallHelper.RunAll(currentPatch, patchLog, currentCulture, isInstall, chosenInstallGroups);
                Application.DoEvents();

                // Showing gui messages
                foreach (string message in PatchHelper.Messages)
                {
                    MessageBoxes.ShowWarning(this, message);
                }

                // OK
                MessageBoxes.ShowInfo(this, isInstall ? _MESSAGE_PATCH_SUCCESS : _MESSAGE_PATCH_UNINSTALL_SUCCESS);

                // Refreshing window contents
                //_LoadData(_CurrentInstallPatch.FileName, (_CurrentUninstallPatch == null) ? null : _CurrentUninstallPatch.FileName);

                // Contextual information has to be updated
                _UpdateContextualControls();
            }
            catch (Exception ex)
            {
                // Showing messages first
                foreach (string message in PatchHelper.Messages)
                {
                    MessageBoxes.ShowWarning(this, message);
                }

                if (PatchHelper.Messages.Count == 0)
                {
                    MessageBoxes.ShowError(this, ex);
                }
            }
            finally
            {
                // Hiding progress bar
                progressPanel.Visible = false;
            }
        }