You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
========================== MAJOR FEATURES + CHANGES ========================== Change 2719147 on 2015/10/07 by Mark.Satterthwaite Allow the shader cache to perform some precompilation synchronously on load before falling back to asynchronous compilation to balance load times against total time spent precompiling. Added a stat to the group that reports how long the precompile has been running until it completes so it is easier to track. Change 2719182 on 2015/10/07 by Mark.Satterthwaite Refactor the ShaderCache's internal data structures and change the way we handle recording whether a particular predraw state has been submitted to try and make it more efficient. Change 2719185 on 2015/10/07 by Mark.Satterthwaite Merging CL #2717701: Try and fix random crashes on Mac when manipulating bound-shader-states caused by ShaderCache potentially providing a bogus shader state pointer on exit from predraw. Change 2719434 on 2015/10/07 by Mark.Satterthwaite Make sure that Mac ensures reports have a source context and a sane callstack when sent to the crash-reports server. Change 2724764 on 2015/10/12 by Josh.Adams [Initial AppleTV support] Merging //depot/YakBranch/... to //UE4/Dev-Platform/... Change 2726266 on 2015/10/13 by Lee.Clark PS4 - Calc reserve size required for DMA copy when using unsafe command buffers Change 2726401 on 2015/10/13 by Mark.Satterthwaite Merging CL #2716418: Fix UE-15228 'Crash Report Client doesn't restart into project editor on Mac' by reporting the original command line supplied by LaunchMac, not the modified one that strips the project name. The CRC can then relaunch as expected. #jira UE-15228 Change 2726421 on 2015/10/13 by Lee.Clark PS4 - Don't try to clear invalid targets Change 2727040 on 2015/10/13 by Michael.Trepka Merging CL 2724777 - Fixed splash screen rendering for images with DPI different than 72 Change 2729783 on 2015/10/15 by Keith.Judge Fix huge memory leak in Test/Shipping configurations, caused because I am a numpty. Change 2729847 on 2015/10/15 by Mark.Satterthwaite Merging CL #2729846: On OS X unconstrain windows from the dimension of the parent display when in Windowed mode - it is OK for them to be larger in this case. They do need to be repositioned if on the Primary display so that they don't creep under the menu bar and become unmovable/unclosable and Fullscreen windows still need to be constrained to a single display. We can now take screenshots of windows that are larger than the display & not get grey bars beyond the cutoff. #jira UE-21992 Change 2729865 on 2015/10/15 by Keith.Judge Fast semantics - Finish up resource transitions, adding resource decompression where appropriate and using non-fast clears where we can't determine the resource transition. Change 2729897 on 2015/10/15 by Keith.Judge Fast Semantics - Make sure all GetData() calls are made safe with GPU fences. Change 2729972 on 2015/10/15 by Keith.Judge Removed the last vestiges of ID3D11DeviceContext/ID3D11DeviceContext1 from the Xbox RHI. Everything now uses ID3D11DeviceContextX directly. This should be marginally quicker as it stops a double call to ClearState(). Change 2731503 on 2015/10/16 by Keith.Judge Added _XDK_VERSION to the DDC key for textures, which should solve the issue of the tiling mode changing in August XDK (and future changes Microsoft may inflict). Change 2731596 on 2015/10/16 by Keith.Judge Fast Semantics - Add deferred resource deletion queue to make deleted resources be actually deleted a number of frames later so that the GPU is definitely finished with them. Hooked up the temporary SRVs for dynamic VBs as a first step. Change 2731928 on 2015/10/16 by Michael.Trepka PR #1659: Mac/Build.sh handles additional arguments (Contributed by judgeaxl) Change 2731934 on 2015/10/16 by Michael.Trepka PR #1618: added clang 3.7.0 -Wshift-negative-value ignore in JpegImageWrapper.cpp (Contributed by bsekura) Change 2732018 on 2015/10/16 by Mark.Satterthwaite Emit a shader code cache for each platforms requested shader formats, this is separate to the targeted formats as not all can or need to be cached. - The implementation extends the ShaderCache's hooks in FShaderResource's serialisation function to capture the required shaders. - Each target platform has its own list of cached shader formats, analogous to the list of targeted RHIs. Presently only the Mac implements this. - Code cached shaders are now compressed (for size) to reduce the overhead associated with keeping all the shader code around - this works esp. well for text-based formats like GLSL. Change 2732365 on 2015/10/16 by Josh.Adams - Packaging a TVOS .ipa now works (still haven't tried any of the Editor integration like Launch On) Change 2733170 on 2015/10/18 by Terence.Burns Fix for Android IAP query not returning entire inventory. Change 2733174 on 2015/10/18 by Terence.Burns Fix Movie player issue where wait for movie to finish isnt being respected. Seems a stray bUserCanceled event flag was causing this not to be observed. Added some verbose logging to apple movie player. Change 2733488 on 2015/10/19 by Mark.Satterthwaite Added the ability to merge the .ushadercache files used by the ShaderCache to store shader & draw state information. - Fixed a bug that would cause invalid shader membership and draw state information to be logged. - Added a separate command-line tool to merge shader cache files, currently Mac-only but in theory should work on other platforms too. Change 2735226 on 2015/10/20 by Mark.Satterthwaite Fix temporal AA rendering on GL/Mac OS X - you can't rely on EyeAdaptation values unless SM5 is available so only perform that code on SM5 & we must correctly clamp saturate(NaN) to 0 as the current hlslcc won't do that for us (& is required by the HLSL spec). The latter used to be clamped in the AA_ALPHA && AA_VELOCITY_WEIGHTING code block that was removed recently. #jira UE-21214 #jira UE-19913 Change 2736722 on 2015/10/21 by Daniel.Lamb Improved performance of cooking stats system. Change 2737172 on 2015/10/21 by Daniel.Lamb Improved cooking stats performance for ddc stats.
2054 lines
62 KiB
C#
2054 lines
62 KiB
C#
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Xml;
|
|
using System.Xml.Linq;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
|
|
namespace UnrealBuildTool
|
|
{
|
|
/* AndroidPluginLanguage (APL) is a simple XML-based language for manipulating XML and returning
|
|
* strings. It contains an <init> section which is evaluated once per architecture before any
|
|
* other sections. The state is maintained and carried forward to the next section evaluated
|
|
* so the order the sections are executed matters.
|
|
*
|
|
* If you need to see the instructions executed in your plugin context add the following to
|
|
* enable tracing:
|
|
*
|
|
* <trace enable="true"/>
|
|
*
|
|
* After this instuction all the nodes actually executed in your context will be written to the
|
|
* log until you do a <trace enable="false"/>. You can also get a dump of all the variables in
|
|
* your context with this command:
|
|
*
|
|
* <dumpvars/>
|
|
*
|
|
* Bool, Int, and String variable types are supported. Any attribute may reference a variable
|
|
* and will be replaced with the string equivalent before evaluation using this syntax:
|
|
*
|
|
* $B(name) = boolean variable "name"'s value
|
|
* $I(name) = integer variable "name"'s value
|
|
* $S(name) = string variable "name"'s value
|
|
* $E(name) = element variable "name"'s value
|
|
*
|
|
* The following variables are initialized automatically:
|
|
*
|
|
* $S(Output) = the output returned for evaluating the section (initialized to Input)
|
|
* $S(Architecture) = target architecture (armeabi-armv7, armeabi-armv8, x86, x86_64)
|
|
* $S(PluginDir) = directory the XML file was loaded from
|
|
* $S(EngineDir) = engine directory
|
|
* $S(BuildDir) = project's Intermediate/Android/APK directory
|
|
* $B(Distribution) = true if distribution build
|
|
*
|
|
* Note: with the exception of the above variables, all are in the context of the plugin to
|
|
* prevent namespace collision; trying to set a new value to any of the above, with the
|
|
* exception of Output, will only affect the current context.
|
|
*
|
|
* The following nodes allow manipulation of variables:
|
|
*
|
|
* <setBool result="" value=""/>
|
|
* <setInt result="" value=""/>
|
|
* <setString result="" value=""/>
|
|
* <setElement result="" value=""/>
|
|
* <setElement result="" xml=""/>
|
|
*
|
|
* <setElement> with value creates an empty XML element with the tag set to value.
|
|
* <setElement> with xml will parse the XML provided. Remember to escape any special characters!
|
|
*
|
|
* Variables may also be set from a property in an ini file:
|
|
*
|
|
* <setBoolFromProperty result="" ini="" section="" property="" default=""/>
|
|
* <setIntFromProperty result="" ini="" section="" property="" default=""/>
|
|
* <setStringFromProperty result="" ini="" section="" property="" default=""/>
|
|
*
|
|
* Boolean variables may also be set to the result of applying operators:
|
|
*
|
|
* <setBoolNot result="" source=""/>
|
|
* <setBoolAnd result="" arg1="" arg2=""/>
|
|
* <setBoolOr result="" arg1="" arg2=""/>
|
|
* <setBoolIsEqual result="" arg1="" arg2=""/>
|
|
* <setBoolIsLess result="" arg1="" arg2=""/>
|
|
* <setBoolIsLessEqual result="" arg1="" arg2=""/>
|
|
* <setBoolIsGreater result="" arg1="" arg2=""/>
|
|
* <setBoolIsGreaterEqual result="" arg1="" arg2=""/>
|
|
*
|
|
* Integer variables may use these arithmetic operations:
|
|
*
|
|
* <setIntAdd result="" arg1="" arg2=""/>
|
|
* <setIntSubtract result="" arg1="" arg2=""/>
|
|
* <setIntMultiply result="" arg1="" arg2=""/>
|
|
* <setIntDivide result="" arg1="" arg2=""/>
|
|
*
|
|
* Strings are manipulated with the following:
|
|
*
|
|
* <setStringAdd result="" arg1="" arg2=""/>
|
|
* <setStringSubstring result="" source="" start="" length=""/>
|
|
* <setStringReplace result="" source="" find="" with=""/>
|
|
*
|
|
* String length may be retrieved with:
|
|
*
|
|
* <setIntLength result="" source=""/>
|
|
*
|
|
* The index of a search string may be found in source with:
|
|
*
|
|
* <setIntFindString result="" source="" find=""/>
|
|
*
|
|
* The following shortcut string comparisons may also be used instead of using <setIntFindString>
|
|
* and checking the result:
|
|
*
|
|
* <setBoolStartsWith result="" source="" find=""/>
|
|
* <setBoolEndsWith result="" source="" find=""/>
|
|
* <setBoolContains result="" source="" find=""/>
|
|
*
|
|
* Messages are written to the log with this node:
|
|
*
|
|
* <log text=""/>
|
|
*
|
|
* Conditional execution uses the following form:
|
|
*
|
|
* <if condition="">
|
|
* <true>
|
|
* <!-- executes if boolean variable in condition is true -->
|
|
* </true>
|
|
* <false>
|
|
* <!-- executes if boolean variable in condition is false -->
|
|
* </false>
|
|
* </if>
|
|
*
|
|
* The <true> and <false> blocks are optional. The condition must be in a boolean variable.
|
|
* The boolean operator nodes may be combined to create a final state for more complex
|
|
* conditions:
|
|
*
|
|
* <setBoolNot result="notDistribution" value="$B(Distribution)/>
|
|
* <setBoolEquals result="isX86" arg1="$S(Architecture)" arg2="x86"/>
|
|
* <setBoolEquals result="isX86_64" arg2="$S(Architecture)" arg2="x86_64/">
|
|
* <setBoolOr result="isIntel" arg1="$B(isX86)" arg2="$B(isX86_64)"/>
|
|
* <setBoolAnd result="intelAndNotDistribution" arg1="$B(isIntel)" arg2="$B(notDistribution)"/>
|
|
* <if condition="intelAndNotDistribution">
|
|
* <true>
|
|
* <!-- do something for Intel if not a distribution build -->
|
|
* </true>
|
|
* </if>
|
|
*
|
|
* Note the "isIntel" could also be done like this:
|
|
*
|
|
* <setStringSubstring result="subarch" source="$S(Architecture)" start="0" length="3"/>
|
|
* <setBoolEquals result="isIntel" arg1="$S(subarch)" arg2="x86"/>
|
|
*
|
|
* Two shortcut nodes are available for conditional execution:
|
|
*
|
|
* <isArch arch="armeabi-armv7">
|
|
* <!-- do stuff -->
|
|
* </isArch>
|
|
*
|
|
* is the equivalent of:
|
|
*
|
|
* <setBoolEquals result="temp" arg1="$S(Architecture)" arg2="armeabi-armv7">
|
|
* <if condition="temp">
|
|
* <true>
|
|
* <!-- do stuff -->
|
|
* </true>
|
|
* </if>
|
|
*
|
|
* and
|
|
*
|
|
* <isDistribution>
|
|
* <!-- do stuff -->
|
|
* </isDistribution>
|
|
*
|
|
* is the equivalent of:
|
|
*
|
|
* <if condition="$B(Distribution)">
|
|
* <!-- do stuff -->
|
|
* </if>
|
|
*
|
|
* Execution may be stopped with:
|
|
*
|
|
* <return/>
|
|
*
|
|
* Loops may be created using these nodes:
|
|
*
|
|
* <while condition="">
|
|
* <!-- do stuff -->
|
|
* </while>
|
|
*
|
|
* <break/>
|
|
* <continue/>
|
|
*
|
|
* The <while> body will execute until the condition is false or a <break/> is hit. The
|
|
* <continue/> will restart execution of the loop if the condition is still true or exit.
|
|
*
|
|
* Note: <break/> outside a <while> body will act the same as <return/>
|
|
*
|
|
* Here is an example loop which writes 1 to 5 to the log, skipping 3. Note the update of the
|
|
* while condition should be done before the continue otherwise it may not exit.
|
|
*
|
|
* <setInt result="index" value="0"/>
|
|
* <setBool result="loopRun" value="true"/>
|
|
* <while condition="loopRun">
|
|
* <setIntAdd result="index" arg1="$I(index)" arg2="1"/>
|
|
* <setBoolIsLess result="loopRun" arg1="$I(index)" arg2="5"/>
|
|
* <setBoolIsEqual result="indexIs3" arg1="$I(index)" arg2="3"/>
|
|
* <if condition="indexIs3">
|
|
* <true>
|
|
* <continue/>
|
|
* </true>
|
|
* </if>
|
|
* <log text="$I(index)"/>
|
|
* </while>
|
|
*
|
|
* It is possible to use variable replacement in generating the result variable
|
|
* name as well. This makes the creation of arrays in loops possible:
|
|
*
|
|
* <setString result="array_$I(index)" value="element $I(index) in array"/>
|
|
*
|
|
* This may be retrieved using the following (value is treated as the variable
|
|
* name):
|
|
*
|
|
* <setStringFrom result="out" value="array_$I(index)"/>
|
|
*
|
|
* For boolean and integer types, you may use <setBoolFrom/> and <setIntFrom/>.
|
|
*
|
|
* Nodes for inserting text into the section are as follows:
|
|
*
|
|
* <insert> body </insert>
|
|
* <insertValue value=""/>
|
|
* <loadLibrary name="" failmsg=""/>
|
|
*
|
|
* The first one will insert either text or nodes into the returned section
|
|
* string. Please note you must use escaped characters for:
|
|
*
|
|
* < = <
|
|
* > = >
|
|
* & = &
|
|
*
|
|
* <insertValue value=""/> evaluates variables in value before insertion. If value contains
|
|
* double quote ("), you must escape it with ".
|
|
*
|
|
* <loadLibrary name="" failmsg=""/> is a shortcut to insert a system.LoadLibrary try/catch
|
|
* block with an optional logged message for failure to load case.
|
|
*
|
|
* You can do a search and replace in the Output with:
|
|
*
|
|
* <replace find="" with=""/>
|
|
*
|
|
* Note you can also manipulate the actual $S(Output) directly, the above are more efficient:
|
|
*
|
|
* <setStringAdd result="Input" arg1="$S(Output)" arg2="sample\n"/>
|
|
* <setStringReplace result="Input" source="$S(Output)" find=".LAUNCH" with=".INFO"/>
|
|
*
|
|
* XML manipulation uses the following nodes:
|
|
*
|
|
* <addElement tag="" name=""/>
|
|
* <addElements tag=""> body </addElements>
|
|
* <removeElement tag=""/>
|
|
* <setStringFromTag result="" tag="" name=""/>
|
|
* <setStringFromAttribute result="" tag="" name=""/>
|
|
* <addAttribute tag="" name="" value=""/>
|
|
* <removeAttribute tag="" name=""/>
|
|
* <loopElements tag=""> instructions </loopElements>
|
|
*
|
|
* The current element is referenced with tag="$". Element variables are referenced with $varname
|
|
* since using $E(varname) will be expanded to the string equivalent of the XML.
|
|
*
|
|
* <uses-permission> and <uses-feature> are updated with:
|
|
*
|
|
* <addPermission android:name="" .. />
|
|
* <addFeature android:name="" .. />
|
|
*
|
|
* Any attributes in the above commands are copied to the element added to the manifest so you
|
|
* can do the following, for example:
|
|
*
|
|
* <addFeature android:name="android.hardware.usb.host" android:required="true"/>
|
|
*
|
|
* Finally, these nodes allow copying of files useful for staging jar and so files:
|
|
*
|
|
* <copyFile src="" dst=""/>
|
|
* <copyDir src="" dst=""/>
|
|
*
|
|
* The following should be used as the base for the src and dst paths:
|
|
*
|
|
* $S(PluginDir) = directory the XML file was loaded from
|
|
* $S(EngineDir) = engine directory
|
|
* $S(BuildDir) = project's Intermediate/Android/APK directory
|
|
*
|
|
* While it is possible to write outside the APK directory, it is not recommended.
|
|
*
|
|
* If you must remove files (like development-only files from distribution builds) you can
|
|
* use this node:
|
|
*
|
|
* <removeFiles mask=""/>
|
|
*
|
|
* It is restricted to only removing files from the BuildDir. Here is example usage to remove
|
|
* the Oculus Signature Files (osig) from the assets directory:
|
|
*
|
|
* <removeFiles mask="assets/oculussig_*"/>
|
|
*
|
|
* The following sections are evaluated during the packaging or deploy stages:
|
|
*
|
|
* <!-- init section is always evaluated once per architecture -->
|
|
* <init> </init>
|
|
*
|
|
* <!-- optional updates applied to AndroidManifest.xml -->
|
|
* <androidManifestUpdates> </androidManifestUpdates>
|
|
*
|
|
* <!-- optional additions to proguard -->
|
|
* <proguardAdditions> </proguardAdditions>
|
|
*
|
|
* <!-- optional files or directories to copy or delete from Intermediate/Android/APK -->
|
|
* <resourceCopies> </resourceCopies>
|
|
*
|
|
* <!-- optional additions to the GameActivity imports in GameActivity.java -->
|
|
* <gameActivityImportAdditions> </gameActivityImportAdditions>
|
|
*
|
|
* <!-- optional additions to the GameActivity class in GameActivity.java -->
|
|
* <gameActivityClassAdditions> </gameActivityOnClassAdditions>
|
|
*
|
|
* <!-- optional additions to GameActivity onCreate metadata reading in GameActivity.java -->
|
|
* <gameActivityReadMetadata> </gameActivityReadMetadata>
|
|
*
|
|
* <!-- optional additions to GameActivity onCreate in GameActivity.java -->
|
|
* <gameActivityOnCreateAdditions> </gameActivityOnCreateAdditions>
|
|
*
|
|
* <!-- optional additions to GameActivity onDestroy in GameActivity.java -->
|
|
* <gameActivityOnDestroyAdditions> </gameActivityOnDestroyAdditions>
|
|
*
|
|
* <!-- optional additions to GameActivity onStart in GameActivity.java -->
|
|
* <gameActivityOnStartAdditions> </gameActivityOnStartAdditions>
|
|
*
|
|
* <!-- optional additions to GameActivity onStop in GameActivity.java -->
|
|
* <gameActivityOnStopAdditions> </gameActivityOnStopAdditions>
|
|
*
|
|
* <!-- optional additions to GameActivity onPause in GameActivity.java -->
|
|
* <gameActivityOnPauseAdditions> </gameActivityOnPauseAdditions>
|
|
*
|
|
* <!-- optional additions to GameActivity onResume in GameActivity.java -->
|
|
* <gameActivityOnResumeAdditions> </gameActivityOnResumeAdditions>
|
|
*
|
|
* <!-- optional additions to GameActivity onActivityResult in GameActivity.java -->
|
|
* <gameActivityOnActivityResultAdditions> </gameActivityOnActivityResultAdditions>
|
|
*
|
|
* <!-- optional libraries to load in GameActivity.java before libUE4.so -->
|
|
* <soLoadLibrary> </soLoadLibrary>
|
|
*
|
|
*
|
|
* Here is the complete list of supported nodes:
|
|
*
|
|
* <isArch arch="">
|
|
* <isDistribution>
|
|
* <if> => <true> / <false>
|
|
* <while condition="">
|
|
* <return/>
|
|
* <break/>
|
|
* <continue/>
|
|
* <log text=""/>
|
|
* <insert> </insert>
|
|
* <insertValue value=""/>
|
|
* <replace find="" with""/>
|
|
* <copyFile src="" dst=""/>
|
|
* <copyDir src="" dst=""/>
|
|
* <loadLibrary name="" failmsg=""/>
|
|
* <setBool result="" value=""/>
|
|
* <setBoolFrom result="" value=""/>
|
|
* <setBoolFromProperty result="" ini="" section="" property="" default=""/>
|
|
* <setBoolNot result="" source=""/>
|
|
* <setBoolAnd result="" arg1="" arg2=""/>
|
|
* <setBoolOr result="" arg1="" arg2=""/>
|
|
* <setBoolIsEqual result="" arg1="" arg2=""/>
|
|
* <setBoolIsLess result="" arg1="" arg2=""/>
|
|
* <setBoolIsLessEqual result="" arg1="" arg2=""/>
|
|
* <setBoolIsGreater result="" arg1="" arg2=""/>
|
|
* <setBoolIsGreaterEqual result="" arg1="" arg2=""/>
|
|
* <setInt result="" value=""/>
|
|
* <setIntFrom result="" value=""/>
|
|
* <setIntFromProperty result="" ini="" section="" property="" default=""/>
|
|
* <setIntAdd result="" arg1="" arg2=""/>
|
|
* <setIntSubtract result="" arg1="" arg2=""/>
|
|
* <setIntMultiply result="" arg1="" arg2=""/>
|
|
* <setIntDivide result="" arg1="" arg2=""/>
|
|
* <setIntLength result="" source=""/>
|
|
* <setIntFindString result="" source="" find=""/>
|
|
* <setString result="" value=""/>
|
|
* <setStringFrom result="" value=""/>
|
|
* <setStringFromProperty result="" ini="" section="" property="" default=""/>
|
|
* <setStringAdd result="" arg1="" arg2=""/>
|
|
* <setStringSubstring result="" source="" index="" length=""/>
|
|
* <setStringReplace result="" source="" find="" with=""/>
|
|
*
|
|
*/
|
|
|
|
public class AndroidPluginLanguage
|
|
{
|
|
/** The merged XML program to run */
|
|
private XDocument XDoc;
|
|
|
|
/** Android namespace */
|
|
private XNamespace AndroidNameSpace;
|
|
|
|
/** Trace flag to enable debugging */
|
|
static private bool bGlobalTrace = false;
|
|
|
|
/** Project file reference */
|
|
private FileReference ProjectFile;
|
|
|
|
static private XDocument XMLDummy = XDocument.Parse("<manifest></manifest>");
|
|
|
|
private class APLContext
|
|
{
|
|
/** Variable state */
|
|
public Dictionary<string, bool> BoolVariables;
|
|
public Dictionary<string, int> IntVariables;
|
|
public Dictionary<string, string> StringVariables;
|
|
public Dictionary<string, XElement> ElementVariables;
|
|
|
|
/** Local context trace */
|
|
public bool bTrace;
|
|
|
|
public APLContext(string Architecture, string PluginDir)
|
|
{
|
|
BoolVariables = new Dictionary<string, bool>();
|
|
IntVariables = new Dictionary<string, int>();
|
|
StringVariables = new Dictionary<string, string>();
|
|
ElementVariables = new Dictionary<string, XElement>();
|
|
|
|
StringVariables["Architecture"] = Architecture;
|
|
StringVariables["PluginDir"] = PluginDir;
|
|
|
|
bTrace = false;
|
|
}
|
|
}
|
|
|
|
private APLContext GlobalContext;
|
|
private Dictionary<string, APLContext> Contexts;
|
|
private int ContextIndex;
|
|
|
|
public AndroidPluginLanguage(FileReference InProjectFile, List<string> XMLFiles, List<string> Architectures)
|
|
{
|
|
ProjectFile = InProjectFile;
|
|
|
|
Contexts = new Dictionary<string, APLContext>();
|
|
GlobalContext = new APLContext("", "");
|
|
ContextIndex = 0;
|
|
|
|
AndroidNameSpace = "http://schemas.android.com/apk/res/android";
|
|
|
|
string PathPrefix = Path.GetFileName(Directory.GetCurrentDirectory()).Equals("Source") ? ".." : "Engine";
|
|
|
|
XDoc = XDocument.Parse("<root xmlns:android=\"http://schemas.android.com/apk/res/android\"></root>");
|
|
foreach (string Basename in XMLFiles)
|
|
{
|
|
string Filename = Path.Combine(PathPrefix, Basename.Replace("\\", "/"));
|
|
Log.TraceInformation("\nAPL: {0}", Filename);
|
|
if (File.Exists(Filename))
|
|
{
|
|
string PluginDir = Path.GetDirectoryName(Filename);
|
|
try
|
|
{
|
|
XDocument MergeDoc = XDocument.Load(Filename);
|
|
MergeXML(MergeDoc, PluginDir, Architectures);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.TraceError("\nAndroid Plugin file {0} parsing failed! {1}", Filename, e);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log.TraceError("\nAndroid Plugin file {0} missing!", Filename);
|
|
Log.TraceInformation("\nCWD: {0}", Directory.GetCurrentDirectory());
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool GetTrace() { return bGlobalTrace; }
|
|
public void SetTrace() { bGlobalTrace = true; }
|
|
public void ClearTrace() { bGlobalTrace = false; }
|
|
|
|
public bool MergeXML(XDocument MergeDoc, string PluginDir, List<string> Architectures)
|
|
{
|
|
if (MergeDoc == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// create a context for each architecture
|
|
ContextIndex++;
|
|
foreach (string Architecture in Architectures)
|
|
{
|
|
APLContext Context = new APLContext(Architecture, PluginDir);
|
|
Contexts[Architecture + "_" + ContextIndex] = Context;
|
|
}
|
|
|
|
// merge in the nodes
|
|
foreach (var Element in MergeDoc.Root.Elements())
|
|
{
|
|
var Parent = XDoc.Root.Element(Element.Name);
|
|
if (Parent != null)
|
|
{
|
|
var Entry = new XElement("Context", new XAttribute("index", ContextIndex.ToString()));
|
|
Entry.Add(Element.Elements());
|
|
Parent.Add(Entry);
|
|
}
|
|
else
|
|
{
|
|
var Entry = new XElement("Context", new XAttribute("index", ContextIndex.ToString()));
|
|
Entry.Add(Element.Elements());
|
|
var Base = new XElement(Element.Name);
|
|
Base.Add(Entry);
|
|
XDoc.Root.Add(Base);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void SaveXML(string Filename)
|
|
{
|
|
if (XDoc != null)
|
|
{
|
|
XDoc.Save(Filename);
|
|
}
|
|
}
|
|
|
|
private string DumpContext(APLContext Context)
|
|
{
|
|
StringBuilder Text = new StringBuilder();
|
|
foreach (var Variable in Context.BoolVariables)
|
|
{
|
|
Text.AppendLine(string.Format("\tbool {0} = {1}", Variable.Key, Variable.Value.ToString().ToLower()));
|
|
}
|
|
foreach (var Variable in Context.IntVariables)
|
|
{
|
|
Text.AppendLine(string.Format("\tint {0} = {1}", Variable.Key, Variable.Value));
|
|
}
|
|
foreach (var Variable in Context.StringVariables)
|
|
{
|
|
Text.AppendLine(string.Format("\tstring {0} = {1}", Variable.Key, Variable.Value));
|
|
}
|
|
foreach (var Variable in Context.ElementVariables)
|
|
{
|
|
Text.AppendLine(string.Format("\telement {0} = {1}", Variable.Key, Variable.Value));
|
|
}
|
|
return Text.ToString();
|
|
}
|
|
|
|
public string DumpVariables()
|
|
{
|
|
string Result = "Global Context:\n" + DumpContext(GlobalContext);
|
|
foreach (var Context in Contexts)
|
|
{
|
|
Result += "Context " + Context.Key + ": " + Context.Value.StringVariables["PluginDir"] + "\n" + DumpContext(Context.Value);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
private bool GetCondition(APLContext Context, XElement Node, string Condition, out bool Result)
|
|
{
|
|
Result = false;
|
|
if (!Context.BoolVariables.TryGetValue(Condition, out Result))
|
|
{
|
|
if (!GlobalContext.BoolVariables.TryGetValue(Condition, out Result))
|
|
{
|
|
Log.TraceWarning("\nMissing condition '{0}' in '{1}' (skipping instruction)", Condition, TraceNodeString(Node));
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private string ExpandVariables(APLContext Context, string InputString)
|
|
{
|
|
string Result = InputString;
|
|
for (int Idx = Result.IndexOf("$B("); Idx != -1; Idx = Result.IndexOf("$B(", Idx))
|
|
{
|
|
// Find the end of the variable name
|
|
int EndIdx = Result.IndexOf(')', Idx + 3);
|
|
if (EndIdx == -1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Extract the variable name from the string
|
|
string Name = Result.Substring(Idx + 3, EndIdx - (Idx + 3));
|
|
|
|
// Find the value for it, either from the dictionary or the environment block
|
|
bool Value;
|
|
if (!Context.BoolVariables.TryGetValue(Name, out Value))
|
|
{
|
|
if (!GlobalContext.BoolVariables.TryGetValue(Name, out Value))
|
|
{
|
|
Idx = EndIdx + 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Replace the variable, or skip past it
|
|
Result = Result.Substring(0, Idx) + Value.ToString().ToLower() + Result.Substring(EndIdx + 1);
|
|
}
|
|
for (int Idx = Result.IndexOf("$I("); Idx != -1; Idx = Result.IndexOf("$I(", Idx))
|
|
{
|
|
// Find the end of the variable name
|
|
int EndIdx = Result.IndexOf(')', Idx + 3);
|
|
if (EndIdx == -1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Extract the variable name from the string
|
|
string Name = Result.Substring(Idx + 3, EndIdx - (Idx + 3));
|
|
|
|
// Find the value for it, either from the dictionary or the environment block
|
|
int Value;
|
|
if (!Context.IntVariables.TryGetValue(Name, out Value))
|
|
{
|
|
if (!GlobalContext.IntVariables.TryGetValue(Name, out Value))
|
|
{
|
|
Idx = EndIdx + 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Replace the variable, or skip past it
|
|
Result = Result.Substring(0, Idx) + Value.ToString() + Result.Substring(EndIdx + 1);
|
|
}
|
|
for (int Idx = Result.IndexOf("$S("); Idx != -1; Idx = Result.IndexOf("$S(", Idx))
|
|
{
|
|
// Find the end of the variable name
|
|
int EndIdx = Result.IndexOf(')', Idx + 3);
|
|
if (EndIdx == -1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Extract the variable name from the string
|
|
string Name = Result.Substring(Idx + 3, EndIdx - (Idx + 3));
|
|
|
|
// Find the value for it, either from the dictionary or the environment block
|
|
string Value;
|
|
if (!Context.StringVariables.TryGetValue(Name, out Value))
|
|
{
|
|
if (!GlobalContext.StringVariables.TryGetValue(Name, out Value))
|
|
{
|
|
Idx = EndIdx + 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Replace the variable, or skip past it
|
|
Result = Result.Substring(0, Idx) + Value + Result.Substring(EndIdx + 1);
|
|
}
|
|
for (int Idx = Result.IndexOf("$E("); Idx != -1; Idx = Result.IndexOf("$E(", Idx))
|
|
{
|
|
// Find the end of the variable name
|
|
int EndIdx = Result.IndexOf(')', Idx + 3);
|
|
if (EndIdx == -1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Extract the variable name from the string
|
|
string Name = Result.Substring(Idx + 3, EndIdx - (Idx + 3));
|
|
|
|
// Find the value for it, either from the dictionary or the environment block
|
|
XElement Value;
|
|
if (!Context.ElementVariables.TryGetValue(Name, out Value))
|
|
{
|
|
if (!GlobalContext.ElementVariables.TryGetValue(Name, out Value))
|
|
{
|
|
Idx = EndIdx + 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Replace the variable, or skip past it
|
|
Result = Result.Substring(0, Idx) + Value + Result.Substring(EndIdx + 1);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
private string TraceNodeString(XElement Node)
|
|
{
|
|
string Result = Node.Name.ToString();
|
|
foreach (var Attrib in Node.Attributes())
|
|
{
|
|
Result += " " + Attrib.ToString();
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
private bool StringToBool(string Input)
|
|
{
|
|
if (Input == null)
|
|
{
|
|
return false;
|
|
}
|
|
Input = Input.ToLower();
|
|
return !(Input.Equals("0") || Input.Equals("false") || Input.Equals("off") || Input.Equals("no"));
|
|
}
|
|
|
|
private int StringToInt(string Input, XElement Node)
|
|
{
|
|
int Result = 0;
|
|
if (!int.TryParse(Input, out Result))
|
|
{
|
|
Log.TraceWarning("\nInvalid integer '{0}' in '{1}' (defaulting to 0)", Input, TraceNodeString(Node));
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
private string GetAttribute(APLContext Context, XElement Node, string AttributeName, bool bExpand = true, bool bRequired = true, string Fallback = null)
|
|
{
|
|
XAttribute Attribute = Node.Attribute(AttributeName);
|
|
if (Attribute == null)
|
|
{
|
|
if (bRequired)
|
|
{
|
|
Log.TraceWarning("\nMissing attribute '{0}' in '{1}' (skipping instruction)", AttributeName, TraceNodeString(Node));
|
|
}
|
|
return Fallback;
|
|
}
|
|
string Result = Attribute.Value;
|
|
return bExpand ? ExpandVariables(Context, Result) : Result;
|
|
}
|
|
|
|
private string GetAttributeWithNamespace(APLContext Context, XElement Node, XNamespace Namespace, string AttributeName, bool bExpand = true, bool bRequired = true, string Fallback = null)
|
|
{
|
|
XAttribute Attribute = Node.Attribute(Namespace + AttributeName);
|
|
if (Attribute == null)
|
|
{
|
|
if (bRequired)
|
|
{
|
|
Log.TraceWarning("\nMissing attribute '{0}' in '{1}' (skipping instruction)", AttributeName, TraceNodeString(Node));
|
|
}
|
|
return Fallback;
|
|
}
|
|
string Result = Attribute.Value;
|
|
return bExpand ? ExpandVariables(Context, Result) : Result;
|
|
}
|
|
|
|
static private Dictionary<string, ConfigCacheIni> ConfigCache = null;
|
|
|
|
private ConfigCacheIni GetConfigCacheIni(string baseIniName)
|
|
{
|
|
if (ConfigCache == null)
|
|
{
|
|
ConfigCache = new Dictionary<string, ConfigCacheIni>();
|
|
}
|
|
ConfigCacheIni config = null;
|
|
if (!ConfigCache.TryGetValue(baseIniName, out config))
|
|
{
|
|
config = ConfigCacheIni.CreateConfigCacheIni(UnrealTargetPlatform.Android, "Engine", DirectoryReference.FromFile(ProjectFile));
|
|
ConfigCache.Add(baseIniName, config);
|
|
}
|
|
return config;
|
|
}
|
|
|
|
private static void CopyFileDirectory(string SourceDir, string DestDir)
|
|
{
|
|
if (!Directory.Exists(SourceDir))
|
|
{
|
|
return;
|
|
}
|
|
|
|
string[] Files = Directory.GetFiles(SourceDir, "*.*", SearchOption.AllDirectories);
|
|
foreach (string Filename in Files)
|
|
{
|
|
// make the dst filename with the same structure as it was in SourceDir
|
|
string DestFilename = Path.Combine(DestDir, Utils.MakePathRelativeTo(Filename, SourceDir));
|
|
if (File.Exists(DestFilename))
|
|
{
|
|
File.Delete(DestFilename);
|
|
}
|
|
|
|
// make the subdirectory if needed
|
|
string DestSubdir = Path.GetDirectoryName(DestFilename);
|
|
if (!Directory.Exists(DestSubdir))
|
|
{
|
|
Directory.CreateDirectory(DestSubdir);
|
|
}
|
|
|
|
File.Copy(Filename, DestFilename);
|
|
|
|
// remove any read only flags
|
|
FileInfo DestFileInfo = new FileInfo(DestFilename);
|
|
DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly;
|
|
}
|
|
}
|
|
|
|
private static void DeleteFiles(string Filespec)
|
|
{
|
|
string BaseDir = Path.GetDirectoryName(Filespec);
|
|
string Mask = Path.GetFileName(Filespec);
|
|
|
|
if (!Directory.Exists(BaseDir))
|
|
{
|
|
return;
|
|
}
|
|
|
|
string[] Files = Directory.GetFiles(BaseDir, Mask, SearchOption.TopDirectoryOnly);
|
|
foreach (string Filename in Files)
|
|
{
|
|
File.Delete(Filename);
|
|
Log.TraceInformation("\nDeleted file {0}", Filename);
|
|
}
|
|
}
|
|
|
|
private void AddAttribute(XElement Element, string Name, string Value)
|
|
{
|
|
XAttribute Attribute;
|
|
int Index = Name.IndexOf(":");
|
|
if (Index >= 0)
|
|
{
|
|
Name = Name.Substring(Index + 1);
|
|
Attribute = Element.Attribute(AndroidNameSpace + Name);
|
|
}
|
|
else
|
|
{
|
|
Attribute = Element.Attribute(Name);
|
|
}
|
|
|
|
if (Attribute != null)
|
|
{
|
|
Attribute.SetValue(Value);
|
|
}
|
|
else
|
|
{
|
|
if (Index >= 0)
|
|
{
|
|
Element.Add(new XAttribute(AndroidNameSpace + Name, Value));
|
|
}
|
|
else
|
|
{
|
|
Element.Add(new XAttribute(Name, Value));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RemoveAttribute(XElement Element, string Name)
|
|
{
|
|
XAttribute Attribute;
|
|
int Index = Name.IndexOf(":");
|
|
if (Index >= 0)
|
|
{
|
|
Name = Name.Substring(Index + 1);
|
|
Attribute = Element.Attribute(AndroidNameSpace + Name);
|
|
}
|
|
else
|
|
{
|
|
Attribute = Element.Attribute(Name);
|
|
}
|
|
|
|
if (Attribute != null)
|
|
{
|
|
Attribute.Remove();
|
|
}
|
|
}
|
|
|
|
private void AddElements(XElement Target, XElement Source)
|
|
{
|
|
if (Source.HasElements)
|
|
{
|
|
foreach (var Index in Source.Elements())
|
|
{
|
|
// if (Target.Element(Index.Name) == null)
|
|
{
|
|
Target.Add(Index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public string ProcessPluginNode(string Architecture, string NodeName, string Input)
|
|
{
|
|
return ProcessPluginNode(Architecture, NodeName, Input, ref XMLDummy);
|
|
}
|
|
|
|
public string ProcessPluginNode(string Architecture, string NodeName, string Input, ref XDocument XMLWork)
|
|
{
|
|
// add all instructions to execution list
|
|
var ExecutionStack = new Stack<XElement>();
|
|
var StartNode = XDoc.Root.Element(NodeName);
|
|
if (StartNode != null)
|
|
{
|
|
foreach (var Instruction in StartNode.Elements().Reverse())
|
|
{
|
|
ExecutionStack.Push(Instruction);
|
|
}
|
|
}
|
|
|
|
if (ExecutionStack.Count == 0)
|
|
{
|
|
return Input;
|
|
}
|
|
|
|
var ContextStack = new Stack<APLContext>();
|
|
APLContext CurrentContext = GlobalContext;
|
|
|
|
// update Output in global context
|
|
GlobalContext.StringVariables["Output"] = Input;
|
|
|
|
var ElementStack = new Stack<XElement>();
|
|
XElement CurrentElement = XMLWork.Elements().First();
|
|
|
|
// run the instructions
|
|
while (ExecutionStack.Count > 0)
|
|
{
|
|
var Node = ExecutionStack.Pop();
|
|
if (bGlobalTrace || CurrentContext.bTrace)
|
|
{
|
|
Log.TraceInformation("Execute: '{0}'", TraceNodeString(Node));
|
|
}
|
|
switch (Node.Name.ToString())
|
|
{
|
|
case "trace":
|
|
{
|
|
string Enable = GetAttribute(CurrentContext, Node, "enable");
|
|
if (Enable != null)
|
|
{
|
|
CurrentContext.bTrace = StringToBool(Enable);
|
|
if (!bGlobalTrace && CurrentContext.bTrace)
|
|
{
|
|
Log.TraceInformation("Context: '{0}' using Architecture='{1}', NodeName='{2}', Input='{3}'", CurrentContext.StringVariables["PluginDir"], Architecture, NodeName, Input);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "dumpvars":
|
|
{
|
|
if (!bGlobalTrace && !CurrentContext.bTrace)
|
|
{
|
|
Log.TraceInformation("Context: '{0}' using Architecture='{1}', NodeName='{2}', Input='{3}'", CurrentContext.StringVariables["PluginDir"], Architecture, NodeName, Input);
|
|
}
|
|
Log.TraceInformation("Variables:\n{0}", DumpContext(CurrentContext));
|
|
}
|
|
break;
|
|
|
|
case "Context":
|
|
{
|
|
ContextStack.Push(CurrentContext);
|
|
string index = GetAttribute(CurrentContext, Node, "index");
|
|
CurrentContext = Contexts[Architecture + "_" + index];
|
|
ExecutionStack.Push(new XElement("PopContext"));
|
|
foreach (var instruction in Node.Elements().Reverse())
|
|
{
|
|
ExecutionStack.Push(instruction);
|
|
}
|
|
|
|
if (bGlobalTrace || CurrentContext.bTrace)
|
|
{
|
|
Log.TraceInformation("Context: '{0}' using Architecture='{1}', NodeName='{2}', Input='{3}'", CurrentContext.StringVariables["PluginDir"], Architecture, NodeName, Input);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "PopContext":
|
|
{
|
|
CurrentContext = ContextStack.Pop();
|
|
}
|
|
break;
|
|
|
|
case "PopElement":
|
|
{
|
|
CurrentElement = ElementStack.Pop();
|
|
}
|
|
break;
|
|
|
|
case "isArch":
|
|
{
|
|
string arch = GetAttribute(CurrentContext, Node, "arch");
|
|
if (arch != null && arch.Equals(Architecture))
|
|
{
|
|
foreach (var instruction in Node.Elements().Reverse())
|
|
{
|
|
ExecutionStack.Push(instruction);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "isDistribution":
|
|
{
|
|
bool Result = false;
|
|
if (GetCondition(CurrentContext, Node, "Distribution", out Result))
|
|
{
|
|
if (Result)
|
|
{
|
|
foreach (var Instruction in Node.Elements().Reverse())
|
|
{
|
|
ExecutionStack.Push(Instruction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "if":
|
|
{
|
|
bool Result;
|
|
if (GetCondition(CurrentContext, Node, GetAttribute(CurrentContext, Node, "condition"), out Result))
|
|
{
|
|
var ResultNode = Node.Element(Result ? "true" : "false");
|
|
if (ResultNode != null)
|
|
{
|
|
foreach (var Instruction in ResultNode.Elements().Reverse())
|
|
{
|
|
ExecutionStack.Push(Instruction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "while":
|
|
{
|
|
bool Result;
|
|
if (GetCondition(CurrentContext, Node, GetAttribute(CurrentContext, Node, "condition"), out Result))
|
|
{
|
|
if (Result)
|
|
{
|
|
var ResultNode = Node.Elements();
|
|
if (ResultNode != null)
|
|
{
|
|
ExecutionStack.Push(Node);
|
|
foreach (var Instruction in ResultNode.Reverse())
|
|
{
|
|
ExecutionStack.Push(Instruction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "return":
|
|
{
|
|
while (ExecutionStack.Count > 0)
|
|
{
|
|
ExecutionStack.Pop();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "break":
|
|
{
|
|
// remove up to while (or acts like a return if outside by removing everything)
|
|
while (ExecutionStack.Count > 0)
|
|
{
|
|
Node = ExecutionStack.Pop();
|
|
if (Node.Name.ToString().Equals("while"))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "continue":
|
|
{
|
|
// remove up to while (or acts like a return if outside by removing everything)
|
|
while (ExecutionStack.Count > 0)
|
|
{
|
|
Node = ExecutionStack.Pop();
|
|
if (Node.Name.ToString().Equals("while"))
|
|
{
|
|
ExecutionStack.Push(Node);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "log":
|
|
{
|
|
string Text = GetAttribute(CurrentContext, Node, "text");
|
|
if (Text != null)
|
|
{
|
|
Log.TraceInformation("{0}", Text);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "loopElements":
|
|
{
|
|
string Tag = GetAttribute(CurrentContext, Node, "tag");
|
|
ElementStack.Push(CurrentElement);
|
|
ExecutionStack.Push(new XElement("PopElement"));
|
|
var WorkList = (Tag == "$") ? CurrentElement.Elements().Reverse() : CurrentElement.Descendants(Tag).Reverse();
|
|
foreach (var WorkNode in WorkList)
|
|
{
|
|
foreach (var Instruction in Node.Elements().Reverse())
|
|
{
|
|
ExecutionStack.Push(Instruction);
|
|
}
|
|
ElementStack.Push(WorkNode);
|
|
ExecutionStack.Push(new XElement("PopElement"));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "addAttribute":
|
|
{
|
|
string Tag = GetAttribute(CurrentContext, Node, "tag");
|
|
string Name = GetAttribute(CurrentContext, Node, "name");
|
|
string Value = GetAttribute(CurrentContext, Node, "value");
|
|
if (Tag != null && Name != null && Value != null)
|
|
{
|
|
if (Tag.StartsWith("$"))
|
|
{
|
|
XElement Target = CurrentElement;
|
|
if (Tag.Length > 1)
|
|
{
|
|
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target))
|
|
{
|
|
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target))
|
|
{
|
|
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
AddAttribute(Target, Name, Value);
|
|
}
|
|
else
|
|
{
|
|
if (CurrentElement.Name.ToString().Equals(Tag))
|
|
{
|
|
AddAttribute(CurrentElement, Name, Value);
|
|
}
|
|
foreach (var WorkNode in CurrentElement.Descendants(Tag))
|
|
{
|
|
AddAttribute(WorkNode, Name, Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "removeAttribute":
|
|
{
|
|
string Tag = GetAttribute(CurrentContext, Node, "tag");
|
|
string Name = GetAttribute(CurrentContext, Node, "name");
|
|
if (Tag != null && Name != null)
|
|
{
|
|
if (Tag.StartsWith("$"))
|
|
{
|
|
XElement Target = CurrentElement;
|
|
if (Tag.Length > 1)
|
|
{
|
|
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target))
|
|
{
|
|
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target))
|
|
{
|
|
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
RemoveAttribute(Target, Name);
|
|
}
|
|
else
|
|
{
|
|
if (CurrentElement.Name.ToString().Equals(Tag))
|
|
{
|
|
RemoveAttribute(CurrentElement, Name);
|
|
}
|
|
foreach (var WorkNode in CurrentElement.Descendants(Tag))
|
|
{
|
|
RemoveAttribute(WorkNode, Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "addPermission":
|
|
{
|
|
string Name = GetAttributeWithNamespace(CurrentContext, Node, AndroidNameSpace, "name");
|
|
if (Name != null)
|
|
{
|
|
// make sure it isn't already added
|
|
bool bFound = false;
|
|
foreach (var Element in XMLWork.Descendants("uses-permission"))
|
|
{
|
|
XAttribute Attribute = Element.Attribute(AndroidNameSpace + "name");
|
|
if (Attribute != null)
|
|
{
|
|
if (Attribute.Value == Name)
|
|
{
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add it if not found
|
|
if (!bFound)
|
|
{
|
|
XMLWork.Element("manifest").Add(new XElement("uses-permission", Node.Attributes()));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "removePermission":
|
|
{
|
|
string Name = GetAttributeWithNamespace(CurrentContext, Node, AndroidNameSpace, "name");
|
|
if (Name != null)
|
|
{
|
|
foreach (var Element in XMLWork.Descendants("uses-permission"))
|
|
{
|
|
XAttribute Attribute = Element.Attribute(AndroidNameSpace + "name");
|
|
if (Attribute != null)
|
|
{
|
|
if (Attribute.Value == Name)
|
|
{
|
|
Element.Remove();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "addFeature":
|
|
{
|
|
string Name = GetAttributeWithNamespace(CurrentContext, Node, AndroidNameSpace, "name");
|
|
if (Name != null)
|
|
{
|
|
// make sure it isn't already added
|
|
bool bFound = false;
|
|
foreach (var Element in XMLWork.Descendants("uses-feature"))
|
|
{
|
|
XAttribute Attribute = Element.Attribute(AndroidNameSpace + "name");
|
|
if (Attribute != null)
|
|
{
|
|
if (Attribute.Value == Name)
|
|
{
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add it if not found
|
|
if (!bFound)
|
|
{
|
|
XMLWork.Element("manifest").Add(new XElement("uses-feature", Node.Attributes()));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "removeFeature":
|
|
{
|
|
string Name = GetAttributeWithNamespace(CurrentContext, Node, AndroidNameSpace, "name");
|
|
if (Name != null)
|
|
{
|
|
foreach (var Element in XMLWork.Descendants("uses-feature"))
|
|
{
|
|
XAttribute Attribute = Element.Attribute(AndroidNameSpace + "name");
|
|
if (Attribute != null)
|
|
{
|
|
if (Attribute.Value == Name)
|
|
{
|
|
Element.Remove();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "removeElement":
|
|
{
|
|
string Tag = GetAttribute(CurrentContext, Node, "tag");
|
|
if (Tag != null)
|
|
{
|
|
if (Tag == "$")
|
|
{
|
|
XElement Parent = CurrentElement.Parent;
|
|
CurrentElement.Remove();
|
|
CurrentElement = Parent;
|
|
}
|
|
else
|
|
{
|
|
// use a list since Remove() may modify it
|
|
foreach (var Element in XMLWork.Descendants(Tag).ToList())
|
|
{
|
|
Element.Remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "addElement":
|
|
{
|
|
string Tag = GetAttribute(CurrentContext, Node, "tag");
|
|
string Name = GetAttribute(CurrentContext, Node, "name");
|
|
if (Tag != null && Name != null)
|
|
{
|
|
XElement Element;
|
|
if (!CurrentContext.ElementVariables.TryGetValue(Name, out Element))
|
|
{
|
|
if (!GlobalContext.ElementVariables.TryGetValue(Name, out Element))
|
|
{
|
|
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node));
|
|
continue;
|
|
}
|
|
}
|
|
if (Tag.StartsWith("$"))
|
|
{
|
|
XElement Target = CurrentElement;
|
|
if (Tag.Length > 1)
|
|
{
|
|
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target))
|
|
{
|
|
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target))
|
|
{
|
|
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
Target.Add(new XElement(Element));
|
|
}
|
|
else
|
|
{
|
|
if (CurrentElement.Name.ToString().Equals(Tag))
|
|
{
|
|
CurrentElement.Add(new XElement(Element));
|
|
}
|
|
foreach (var WorkNode in CurrentElement.Descendants(Tag))
|
|
{
|
|
WorkNode.Add(new XElement(Element));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "addElements":
|
|
{
|
|
string Tag = GetAttribute(CurrentContext, Node, "tag");
|
|
if (Tag != null)
|
|
{
|
|
if (Tag.StartsWith("$"))
|
|
{
|
|
XElement Target = CurrentElement;
|
|
if (Tag.Length > 1)
|
|
{
|
|
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target))
|
|
{
|
|
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target))
|
|
{
|
|
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
AddElements(Target, Node);
|
|
}
|
|
else
|
|
{
|
|
if (CurrentElement.Name.ToString().Equals(Tag))
|
|
{
|
|
AddElements(CurrentElement, Node);
|
|
}
|
|
foreach (var WorkNode in CurrentElement.Descendants(Tag))
|
|
{
|
|
AddElements(WorkNode, Node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "insert":
|
|
{
|
|
if (Node.HasElements)
|
|
{
|
|
foreach (var Element in Node.Elements())
|
|
{
|
|
string Value = Element.ToString().Replace(" xmlns:android=\"http://schemas.android.com/apk/res/android\" ", "");
|
|
GlobalContext.StringVariables["Output"] += Value + "\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string Value = Node.Value.ToString();
|
|
|
|
// trim trailing tabs
|
|
int Index = Value.Length;
|
|
while (Index > 0 && Value[Index - 1] == '\t')
|
|
{
|
|
Index--;
|
|
}
|
|
if (Index < Value.Length)
|
|
{
|
|
Value = Value.Substring(0, Index);
|
|
}
|
|
|
|
// trim leading newlines
|
|
Index = 0;
|
|
while (Index < Value.Length && Value[Index] == '\n')
|
|
{
|
|
Index++;
|
|
}
|
|
|
|
if (Index < Value.Length)
|
|
{
|
|
GlobalContext.StringVariables["Output"] += Value.Substring(Index);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "insertValue":
|
|
{
|
|
string Value = GetAttribute(CurrentContext, Node, "value");
|
|
if (Value != null)
|
|
{
|
|
GlobalContext.StringVariables["Output"] += Value;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "replace":
|
|
{
|
|
string Find = GetAttribute(CurrentContext, Node, "find");
|
|
string With = GetAttribute(CurrentContext, Node, "with");
|
|
if (Find != null && With != null)
|
|
{
|
|
GlobalContext.StringVariables["Output"] = GlobalContext.StringVariables["Output"].Replace(Find, With);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "copyFile":
|
|
{
|
|
string Src = GetAttribute(CurrentContext, Node, "src");
|
|
string Dst = GetAttribute(CurrentContext, Node, "dst");
|
|
if (Src != null && Dst != null)
|
|
{
|
|
if (File.Exists(Src))
|
|
{
|
|
// check to see if newer than last time we copied
|
|
bool bFileExists = File.Exists(Dst);
|
|
TimeSpan Diff = File.GetLastWriteTimeUtc(Dst) - File.GetLastWriteTimeUtc(Src);
|
|
if (!bFileExists || Diff.TotalSeconds < -1 || Diff.TotalSeconds > 1)
|
|
{
|
|
if (bFileExists)
|
|
{
|
|
File.Delete(Dst);
|
|
}
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Dst));
|
|
File.Copy(Src, Dst, true);
|
|
Log.TraceInformation("\nFile {0} copied to {1}", Src, Dst);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "copyDir":
|
|
{
|
|
string Src = GetAttribute(CurrentContext, Node, "src");
|
|
string Dst = GetAttribute(CurrentContext, Node, "dst");
|
|
if (Src != null && Dst != null)
|
|
{
|
|
CopyFileDirectory(Src, Dst);
|
|
Log.TraceInformation("\nDirectory {0} copied to {1}", Src, Dst);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "deleteFiles":
|
|
{
|
|
string Filespec = GetAttribute(CurrentContext, Node, "filespec");
|
|
if (Filespec != null)
|
|
{
|
|
if (Filespec.Contains(":") || Filespec.Contains(".."))
|
|
{
|
|
Log.TraceInformation("\nFilespec {0} not allowed; ignored.", Filespec);
|
|
}
|
|
else
|
|
{
|
|
// force relative to BuildDir (and only from global context so someone doesn't try to be clever)
|
|
DeleteFiles(Path.Combine(GlobalContext.StringVariables["BuildDir"], Filespec));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "loadLibrary":
|
|
{
|
|
string Name = GetAttribute(CurrentContext, Node, "name");
|
|
string FailMsg = GetAttribute(CurrentContext, Node, "failmsg", true, false);
|
|
if (Name != null)
|
|
{
|
|
string Work = "\t\ttry\n" +
|
|
"\t\t{\n" +
|
|
"\t\t\tSystem.loadLibrary(\"" + Name + "\");\n" +
|
|
"\t\t}\n" +
|
|
"\t\tcatch (java.lang.UnsatisfiedLinkError e)\n" +
|
|
"\t\t{\n";
|
|
if (FailMsg != null)
|
|
{
|
|
Work += "\t\t\tLog.debug(\"" + FailMsg + "\");\n";
|
|
}
|
|
GlobalContext.StringVariables["Output"] += Work + "\t\t}\n";
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBool":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Value = GetAttribute(CurrentContext, Node, "value", true, false, "false");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = StringToBool(Value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolFrom":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Value = GetAttribute(CurrentContext, Node, "value", true, false, "false");
|
|
if (Result != null)
|
|
{
|
|
Value = ExpandVariables(CurrentContext, "$B(" + Value + ")");
|
|
CurrentContext.BoolVariables[Result] = StringToBool(Value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolFromProperty":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Ini = GetAttribute(CurrentContext, Node, "ini");
|
|
string Section = GetAttribute(CurrentContext, Node, "section");
|
|
string Property = GetAttribute(CurrentContext, Node, "property");
|
|
string DefaultVal = GetAttribute(CurrentContext, Node, "default", true, false, "false");
|
|
if (Result != null && Ini != null && Section != null && Property != null)
|
|
{
|
|
bool Value = StringToBool(DefaultVal);
|
|
|
|
ConfigCacheIni ConfigIni = GetConfigCacheIni(Ini);
|
|
if (ConfigIni != null)
|
|
{
|
|
ConfigIni.GetBool(Section, Property, out Value);
|
|
}
|
|
CurrentContext.BoolVariables[Result] = Value;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolContains":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Source = GetAttribute(CurrentContext, Node, "source", true, false, "");
|
|
string Find = GetAttribute(CurrentContext, Node, "find", true, false, "");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = Source.Contains(Find);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolStartsWith":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Source = GetAttribute(CurrentContext, Node, "source", true, false, "");
|
|
string Find = GetAttribute(CurrentContext, Node, "find", true, false, "");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = Source.StartsWith(Find);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolEndsWith":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Source = GetAttribute(CurrentContext, Node, "source", true, false, "");
|
|
string Find = GetAttribute(CurrentContext, Node, "find", true, false, "");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = Source.EndsWith(Find);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolNot":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Source = GetAttribute(CurrentContext, Node, "source", true, false, "false");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = !StringToBool(Source);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolAnd":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "false");
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "false");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = StringToBool(Arg1) && StringToBool(Arg2);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolOr":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "false");
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "false");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = StringToBool(Arg1) || StringToBool(Arg2);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolIsEqual":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "");
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = Arg1.Equals(Arg2);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolIsLess":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false);
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false);
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = (StringToInt(Arg1, Node) < StringToInt(Arg2, Node));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolIsLessEqual":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false);
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false);
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = (StringToInt(Arg1, Node) <= StringToInt(Arg2, Node));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolIsGreater":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false);
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false);
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = (StringToInt(Arg1, Node) > StringToInt(Arg2, Node));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setBoolIsGreaterEqual":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false);
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false);
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.BoolVariables[Result] = (StringToInt(Arg1, Node) >= StringToInt(Arg2, Node));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setInt":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Value = GetAttribute(CurrentContext, Node, "value", true, false, "0");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.IntVariables[Result] = StringToInt(Value, Node);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setIntFrom":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Value = GetAttribute(CurrentContext, Node, "value", true, false, "0");
|
|
if (Result != null)
|
|
{
|
|
Value = ExpandVariables(CurrentContext, "$I(" + Value + ")");
|
|
CurrentContext.IntVariables[Result] = StringToInt(Value, Node);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setIntFromProperty":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Ini = GetAttribute(CurrentContext, Node, "ini");
|
|
string Section = GetAttribute(CurrentContext, Node, "section");
|
|
string Property = GetAttribute(CurrentContext, Node, "property");
|
|
string DefaultVal = GetAttribute(CurrentContext, Node, "default", true, false, "0");
|
|
if (Result != null && Ini != null && Section != null && Property != null)
|
|
{
|
|
int Value = StringToInt(DefaultVal, Node);
|
|
|
|
ConfigCacheIni ConfigIni = GetConfigCacheIni(Ini);
|
|
if (ConfigIni != null)
|
|
{
|
|
ConfigIni.GetInt32(Section, Property, out Value);
|
|
}
|
|
CurrentContext.IntVariables[Result] = Value;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setIntAdd":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "0");
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "0");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.IntVariables[Result] = StringToInt(Arg1, Node) + StringToInt(Arg2, Node);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setIntSubtract":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "0");
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "0");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.IntVariables[Result] = StringToInt(Arg1, Node) - StringToInt(Arg2, Node);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setIntMultiply":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "1");
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "1");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.IntVariables[Result] = StringToInt(Arg1, Node) * StringToInt(Arg2, Node);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setIntDivide":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "1");
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "1");
|
|
if (Result != null)
|
|
{
|
|
int Denominator = StringToInt(Arg2, Node);
|
|
if (Denominator == 0)
|
|
{
|
|
CurrentContext.IntVariables[Result] = StringToInt(Arg1, Node);
|
|
}
|
|
else
|
|
{
|
|
CurrentContext.IntVariables[Result] = StringToInt(Arg1, Node) / Denominator;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setIntLength":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Source = GetAttribute(CurrentContext, Node, "source", true, false, "");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.IntVariables[Result] = Source.Length;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setIntFindString":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Source = GetAttribute(CurrentContext, Node, "source", true, false, "");
|
|
string Find = GetAttribute(CurrentContext, Node, "find", true, false, "");
|
|
if (Result != null)
|
|
{
|
|
CurrentContext.IntVariables[Result] = Source.IndexOf(Find);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setString":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Value = GetAttribute(CurrentContext, Node, "value", true, false, "");
|
|
if (Result != null)
|
|
{
|
|
if (Result == "Output")
|
|
{
|
|
GlobalContext.StringVariables["Output"] = Value;
|
|
}
|
|
else
|
|
{
|
|
CurrentContext.StringVariables[Result] = Value;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setStringFrom":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Value = GetAttribute(CurrentContext, Node, "value", true, false, "0");
|
|
if (Result != null)
|
|
{
|
|
Value = ExpandVariables(CurrentContext, "$S(" + Value + ")");
|
|
CurrentContext.StringVariables[Result] = Value;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setStringFromTag":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Tag = GetAttribute(CurrentContext, Node, "tag", true, false, "$");
|
|
if (Result != null)
|
|
{
|
|
XElement Element = CurrentElement;
|
|
if (Tag.StartsWith("$"))
|
|
{
|
|
if (Tag.Length > 1)
|
|
{
|
|
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Element))
|
|
{
|
|
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Element))
|
|
{
|
|
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CurrentContext.StringVariables[Result] = Element.Name.ToString();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setStringFromAttribute":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Tag = GetAttribute(CurrentContext, Node, "tag");
|
|
string Name = GetAttribute(CurrentContext, Node, "name");
|
|
if (Result != null && Tag != null && Name != null)
|
|
{
|
|
XElement Element = CurrentElement;
|
|
if (Tag.StartsWith("$"))
|
|
{
|
|
if (Tag.Length > 1)
|
|
{
|
|
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Element))
|
|
{
|
|
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Element))
|
|
{
|
|
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
XAttribute Attribute;
|
|
int Index = Name.IndexOf(":");
|
|
if (Index >= 0)
|
|
{
|
|
Name = Name.Substring(Index + 1);
|
|
Attribute = Element.Attribute(AndroidNameSpace + Name);
|
|
}
|
|
else
|
|
{
|
|
Attribute = Element.Attribute(Name);
|
|
}
|
|
|
|
CurrentContext.StringVariables[Result] = (Attribute != null) ? Attribute.Value : "";
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setStringFromProperty":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Ini = GetAttribute(CurrentContext, Node, "ini");
|
|
string Section = GetAttribute(CurrentContext, Node, "section");
|
|
string Property = GetAttribute(CurrentContext, Node, "property");
|
|
string DefaultVal = GetAttribute(CurrentContext, Node, "default", true, false, "");
|
|
if (Result != null && Ini != null && Section != null && Property != null)
|
|
{
|
|
string Value = DefaultVal;
|
|
|
|
ConfigCacheIni ConfigIni = GetConfigCacheIni(Ini);
|
|
if (ConfigIni != null)
|
|
{
|
|
ConfigIni.GetString(Section, Property, out Value);
|
|
}
|
|
if (Result == "Output")
|
|
{
|
|
GlobalContext.StringVariables["Output"] = Value;
|
|
}
|
|
else
|
|
{
|
|
CurrentContext.StringVariables[Result] = Value;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setStringAdd":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "");
|
|
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "");
|
|
if (Result != null)
|
|
{
|
|
string Value = Arg1 + Arg2;
|
|
if (Result == "Output")
|
|
{
|
|
GlobalContext.StringVariables["Output"] = Value;
|
|
}
|
|
else
|
|
{
|
|
CurrentContext.StringVariables[Result] = Value;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setStringSubstring":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Source = GetAttribute(CurrentContext, Node, "source", true, false, "");
|
|
string Start = GetAttribute(CurrentContext, Node, "start", true, false, "0");
|
|
string Length = GetAttribute(CurrentContext, Node, "length", true, false, "0");
|
|
if (Result != null && Source != null)
|
|
{
|
|
int Index = StringToInt(Start, Node);
|
|
int Count = StringToInt(Length, Node);
|
|
Index = (Index < 0) ? 0 : (Index > Source.Length) ? Source.Length : Index;
|
|
Count = (Index + Count > Source.Length) ? Source.Length - Index : Count;
|
|
string Value = Source.Substring(Index, Count);
|
|
if (Result == "Output")
|
|
{
|
|
GlobalContext.StringVariables["Output"] = Value;
|
|
}
|
|
else
|
|
{
|
|
CurrentContext.StringVariables[Result] = Value;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setStringReplace":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Source = GetAttribute(CurrentContext, Node, "source", true, false, "");
|
|
string Find = GetAttribute(CurrentContext, Node, "find");
|
|
string With = GetAttribute(CurrentContext, Node, "with", true, false, "");
|
|
if (Result != null && Find != null)
|
|
{
|
|
string Value = Source.Replace(Find, With);
|
|
if (Result == "Output")
|
|
{
|
|
GlobalContext.StringVariables["Output"] = Value;
|
|
}
|
|
else
|
|
{
|
|
CurrentContext.StringVariables[Result] = Value;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "setElement":
|
|
{
|
|
string Result = GetAttribute(CurrentContext, Node, "result");
|
|
string Value = GetAttribute(CurrentContext, Node, "value", true, false);
|
|
string Parse = GetAttribute(CurrentContext, Node, "xml", true, false);
|
|
if (Result != null)
|
|
{
|
|
if (Value != null)
|
|
{
|
|
CurrentContext.ElementVariables[Result] = new XElement(Value);
|
|
}
|
|
else if (Parse != null)
|
|
{
|
|
try
|
|
{
|
|
CurrentContext.ElementVariables[Result] = XElement.Parse(Parse);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.TraceError("\nXML parsing {0} failed! {1} (skipping instruction)", Parse, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Log.TraceWarning("\nUnknown command: {0}", Node.Name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return GlobalContext.StringVariables["Output"];
|
|
}
|
|
|
|
public void Init(List<string> Architectures, bool bDistribution, string EngineDirectory, string BuildDirectory)
|
|
{
|
|
GlobalContext.BoolVariables["Distribution"] = bDistribution;
|
|
GlobalContext.StringVariables["EngineDir"] = EngineDirectory;
|
|
GlobalContext.StringVariables["BuildDir"] = BuildDirectory;
|
|
|
|
foreach (string Arch in Architectures)
|
|
{
|
|
Log.TraceInformation("APL Init: {0}", Arch);
|
|
ProcessPluginNode(Arch, "init", "");
|
|
}
|
|
|
|
if (bGlobalTrace)
|
|
{
|
|
Log.TraceInformation("\nVariables:\n{0}", DumpVariables());
|
|
}
|
|
}
|
|
}
|
|
}
|