nightward/Assets/GUPS/AntiCheat/Editor/Source/Build/PostProcessAndroidBuild.cs

270 lines
12 KiB
C#

// 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
{
/// <summary>
/// Handles post-processing of the Gradle Android project after it is generated by Unity.
/// Implements the <see cref="IPostGenerateGradleAndroidProject"/> interface.
/// </summary>
/// <remarks>
/// This class modifies the AndroidManifest.xml file of the generated Gradle project by adding
/// specific `<package>` elements under the `<queries>` section to ensure compatibility with Android's
/// runtime permissions model.
/// </remarks>
internal class PostProcessAndroidBuild : IPostGenerateGradleAndroidProject
{
/// <summary>
/// Gets the callback order of this postprocessor.
/// </summary>
/// <remarks>
/// The callback order is set to a high value (<see cref="Int32.MaxValue"/> - 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.
/// </remarks>
public int callbackOrder => Int32.MaxValue - 1;
/// <summary>
/// Retrieves the path to the `AndroidManifest.xml` file within the generated Gradle project.
/// </summary>
/// <param name="_BasePath">The base path of the Gradle project.</param>
/// <returns>The full file path to the `AndroidManifest.xml` file.</returns>
/// <remarks>
/// 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`.
/// </remarks>
private String GetManifestPath(String _BasePath)
{
return Path.Combine(_BasePath, "src", "main", "AndroidManifest.xml");
}
/// <summary>
/// Processes the generated Gradle Android project after it is created by Unity.
/// </summary>
/// <param name="_BasePath">The base path of the generated Gradle project.</param>
/// <remarks>
/// This method performs the following steps:
/// <list type="number">
/// <item>Locates the `AndroidManifest.xml` file in the Gradle project.</item>
/// <item>Loads the manifest using the <see cref="AndroidManifest"/> class.</item>
/// <item>Adds `<package>` elements under the `<queries>` section for applications specified in the global settings.</item>
/// <item>Saves the updated manifest back to the file system.</item>
/// </list>
/// </remarks>
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<String> 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
}
/// <summary>
/// Retrieves the list of application package names to add to the `<queries>` section of the manifest.
/// </summary>
/// <returns>A list of application package names, or an empty list if no global settings are available.</returns>
/// <remarks>
/// 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.
/// </remarks>
private List<String> GetAppPackagesToFind()
{
return GlobalSettings.Instance?.Android_BlacklistedApplications ?? new List<String>();
}
/// <summary>
/// Represents an Android XML document that handles Android-specific XML namespace and file operations.
/// </summary>
/// <remarks>
/// This class extends XmlDocument to provide specialized functionality for working with Android XML files,
/// including proper namespace management and file handling.
/// </remarks>
internal class AndroidXmlDocument : XmlDocument
{
/// <summary>
/// The file path of the Android XML document.
/// </summary>
private String filePath;
/// <summary>
/// The namespace manager for handling XML namespaces in the document.
/// </summary>
protected XmlNamespaceManager namespaceManager;
/// <summary>
/// The standard Android XML namespace URI.
/// </summary>
public readonly String AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
/// <summary>
/// Initializes a new instance of the AndroidXmlDocument class.
/// </summary>
/// <param name="_Path">The file path of the Android XML document to load.</param>
/// <remarks>
/// Loads the XML document from the specified path and initializes the namespace manager
/// with the Android XML namespace.
/// </remarks>
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);
}
/// <summary>
/// Saves the XML document to its original file path.
/// </summary>
/// <remarks>
/// Uses the file path specified during initialization to save the document.
/// </remarks>
public void Save()
{
this.SaveAt(this.filePath);
}
/// <summary>
/// Saves the XML document to a specified file path.
/// </summary>
/// <param name="_Path">The file path where the XML document should be saved.</param>
/// <remarks>
/// Saves the document with proper formatting and UTF-8 encoding without BOM (Byte Order Mark).
/// </remarks>
public void SaveAt(String _Path)
{
using (var var_Writer = new XmlTextWriter(_Path, new UTF8Encoding(false)))
{
var_Writer.Formatting = Formatting.Indented;
this.Save(var_Writer);
}
}
}
/// <summary>
/// Represents an AndroidManifest.xml document and provides methods for common operations
/// such as adding permissions or query packages.
/// Inherits from <see cref="AndroidXmlDocument"/>.
/// </summary>
/// <remarks>
/// This class simplifies the management of AndroidManifest.xml files by providing methods
/// to manipulate key elements, such as `<uses-permission>` and `<queries>`.
/// </remarks>
internal class AndroidManifest : AndroidXmlDocument
{
/// <summary>
/// The root `<manifest>` element of the AndroidManifest.xml document.
/// </summary>
private readonly XmlElement ManifestElement;
/// <summary>
/// Initializes a new instance of the <see cref="AndroidManifest"/> class.
/// </summary>
/// <param name="_Path">The file path of the AndroidManifest.xml document to load.</param>
/// <remarks>
/// The constructor loads the AndroidManifest.xml file and initializes the root `<manifest>` element for further manipulation.
/// </remarks>
public AndroidManifest(String _Path) : base(_Path)
{
// Select the root <manifest> element.
this.ManifestElement = this.SelectSingleNode("/manifest") as XmlElement;
}
/// <summary>
/// Creates an Android-specific XML attribute with the specified key and value.
/// </summary>
/// <param name="key">The key of the Android attribute (e.g., "name").</param>
/// <param name="value">The value of the attribute.</param>
/// <returns>A new <see cref="XmlAttribute"/> instance representing the Android attribute.</returns>
/// <remarks>
/// This method simplifies the creation of namespaced attributes for the Android XML namespace.
/// </remarks>
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;
}
/// <summary>
/// Adds a `<uses-permission>` element to the AndroidManifest.xml document.
/// </summary>
/// <param name="_Permission">The name of the permission to add (e.g., "android.permission.INTERNET").</param>
/// <remarks>
/// This method appends a `<uses-permission>` element with the specified permission to the root `<manifest>` element.
/// </remarks>
public void AddUsesPermission(String _Permission)
{
// Create a <uses-permission> 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);
}
/// <summary>
/// Adds a `<package>` element under the `<queries>` section in the AndroidManifest.xml document.
/// </summary>
/// <param name="_Name">The name of the package to add (e.g., "com.example.app").</param>
/// <remarks>
/// This method ensures that a `<queries>` element exists in the manifest and appends a `<package>` element
/// with the specified package name to it.
/// </remarks>
public void AddQueryPackage(String _Name)
{
// Retrieve or create the <queries> element.
XmlElement queryPackageElement = this.ManifestElement.SelectSingleNode("queries") as XmlElement;
if (queryPackageElement == null)
{
// If <queries> does not exist, create it.
queryPackageElement = this.CreateElement("queries");
this.ManifestElement.AppendChild(queryPackageElement);
}
// Create the <package> 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);
}
}
}
}