// Microsoft
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
// Unity
using UnityEditor.Android;
// GUPS - AntiCheat
using GUPS.AntiCheat.Settings;
namespace GUPS.AntiCheat.Editor.Build
{
///
/// Handles post-processing of the Gradle Android project after it is generated by Unity.
/// Implements the interface.
///
///
/// This class modifies the AndroidManifest.xml file of the generated Gradle project by adding
/// specific `` elements under the `` section to ensure compatibility with Android's
/// runtime permissions model.
///
internal class PostProcessAndroidBuild : IPostGenerateGradleAndroidProject
{
///
/// Gets the callback order of this postprocessor.
///
///
/// The callback order is set to a high value ( - 1) to ensure
/// that this postprocessor is executed near the very end of the post-processing pipeline.
/// This allows it to modify the AndroidManifest.xml as the final step without overwriting
/// changes made by other postprocessors.
///
public int callbackOrder => Int32.MaxValue - 1;
///
/// Retrieves the path to the `AndroidManifest.xml` file within the generated Gradle project.
///
/// The base path of the Gradle project.
/// The full file path to the `AndroidManifest.xml` file.
///
/// The method constructs the path by combining the base path with the relative path to the manifest file,
/// which is typically located in `src/main/AndroidManifest.xml`.
///
private String GetManifestPath(String _BasePath)
{
return Path.Combine(_BasePath, "src", "main", "AndroidManifest.xml");
}
///
/// Processes the generated Gradle Android project after it is created by Unity.
///
/// The base path of the generated Gradle project.
///
/// This method performs the following steps:
///
/// - Locates the `AndroidManifest.xml` file in the Gradle project.
/// - Loads the manifest using the class.
/// - Adds `` elements under the `` section for applications specified in the global settings.
/// - Saves the updated manifest back to the file system.
///
///
public void OnPostGenerateGradleAndroidProject(String _BasePath)
{
#if UNITY_ANDROID
// Get the path to the AndroidManifest.xml file.
String var_ManifestPath = this.GetManifestPath(_BasePath);
// Load the AndroidManifest.xml file.
AndroidManifest var_Manifest = new AndroidManifest(var_ManifestPath);
// Add the packages into the AndroidManifest.xml file.
List var_AppPackages = this.GetAppPackagesToFind();
for (int i = 0; i < var_AppPackages.Count; i++)
{
var_Manifest.AddQueryPackage(var_AppPackages[i]);
}
// Save the changes to the AndroidManifest.xml file.
var_Manifest.Save();
#endif
}
///
/// Retrieves the list of application package names to add to the `` section of the manifest.
///
/// A list of application package names, or an empty list if no global settings are available.
///
/// The list of applications is determined by accessing the global settings instance and retrieving
/// the `Android_BlacklistedApplications` property. If the global settings are unavailable, an empty
/// list is returned.
///
private List GetAppPackagesToFind()
{
return GlobalSettings.Instance?.Android_BlacklistedApplications ?? new List();
}
///
/// Represents an Android XML document that handles Android-specific XML namespace and file operations.
///
///
/// This class extends XmlDocument to provide specialized functionality for working with Android XML files,
/// including proper namespace management and file handling.
///
internal class AndroidXmlDocument : XmlDocument
{
///
/// The file path of the Android XML document.
///
private String filePath;
///
/// The namespace manager for handling XML namespaces in the document.
///
protected XmlNamespaceManager namespaceManager;
///
/// The standard Android XML namespace URI.
///
public readonly String AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
///
/// Initializes a new instance of the AndroidXmlDocument class.
///
/// The file path of the Android XML document to load.
///
/// Loads the XML document from the specified path and initializes the namespace manager
/// with the Android XML namespace.
///
public AndroidXmlDocument(String _Path)
{
this.filePath = _Path;
using (var var_Reader = new XmlTextReader(this.filePath))
{
var_Reader.Read();
this.Load(var_Reader);
}
this.namespaceManager = new XmlNamespaceManager(this.NameTable);
this.namespaceManager.AddNamespace("android", this.AndroidXmlNamespace);
}
///
/// Saves the XML document to its original file path.
///
///
/// Uses the file path specified during initialization to save the document.
///
public void Save()
{
this.SaveAt(this.filePath);
}
///
/// Saves the XML document to a specified file path.
///
/// The file path where the XML document should be saved.
///
/// Saves the document with proper formatting and UTF-8 encoding without BOM (Byte Order Mark).
///
public void SaveAt(String _Path)
{
using (var var_Writer = new XmlTextWriter(_Path, new UTF8Encoding(false)))
{
var_Writer.Formatting = Formatting.Indented;
this.Save(var_Writer);
}
}
}
///
/// Represents an AndroidManifest.xml document and provides methods for common operations
/// such as adding permissions or query packages.
/// Inherits from .
///
///
/// This class simplifies the management of AndroidManifest.xml files by providing methods
/// to manipulate key elements, such as `` and ``.
///
internal class AndroidManifest : AndroidXmlDocument
{
///
/// The root `` element of the AndroidManifest.xml document.
///
private readonly XmlElement ManifestElement;
///
/// Initializes a new instance of the class.
///
/// The file path of the AndroidManifest.xml document to load.
///
/// The constructor loads the AndroidManifest.xml file and initializes the root `` element for further manipulation.
///
public AndroidManifest(String _Path) : base(_Path)
{
// Select the root element.
this.ManifestElement = this.SelectSingleNode("/manifest") as XmlElement;
}
///
/// Creates an Android-specific XML attribute with the specified key and value.
///
/// The key of the Android attribute (e.g., "name").
/// The value of the attribute.
/// A new instance representing the Android attribute.
///
/// This method simplifies the creation of namespaced attributes for the Android XML namespace.
///
private XmlAttribute CreateAndroidAttribute(String key, String value)
{
// Create an attribute in the Android namespace.
XmlAttribute attr = CreateAttribute("android", key, this.AndroidXmlNamespace);
attr.Value = value;
return attr;
}
///
/// Adds a `` element to the AndroidManifest.xml document.
///
/// The name of the permission to add (e.g., "android.permission.INTERNET").
///
/// This method appends a `` element with the specified permission to the root `` element.
///
public void AddUsesPermission(String _Permission)
{
// Create a element.
XmlElement child = this.CreateElement("uses-permission");
this.ManifestElement.AppendChild(child);
// Create and append the "android:name" attribute.
XmlAttribute newAttribute = this.CreateAndroidAttribute("name", _Permission);
child.Attributes.Append(newAttribute);
}
///
/// Adds a `` element under the `` section in the AndroidManifest.xml document.
///
/// The name of the package to add (e.g., "com.example.app").
///
/// This method ensures that a `` element exists in the manifest and appends a `` element
/// with the specified package name to it.
///
public void AddQueryPackage(String _Name)
{
// Retrieve or create the element.
XmlElement queryPackageElement = this.ManifestElement.SelectSingleNode("queries") as XmlElement;
if (queryPackageElement == null)
{
// If does not exist, create it.
queryPackageElement = this.CreateElement("queries");
this.ManifestElement.AppendChild(queryPackageElement);
}
// Create the element.
XmlElement packageElement = this.CreateElement("package");
queryPackageElement.AppendChild(packageElement);
// Create and append the "android:name" attribute.
XmlAttribute newAttribute = this.CreateAndroidAttribute("name", _Name);
packageElement.Attributes.Append(newAttribute);
}
}
}
}