Compress Xamarin Android APK assemblies - xamarin.android

Is it possible to compress Xamarin assemblies into assemblies.blob after decompressing them using decompress-assemblies command line tool included in xamarin-android?

I got familiar with the structure of assemblies.blob file from https://github.com/xamarin/xamarin-android/blob/main/Documentation/project-docs/AssemblyStores.md and used the following to be able to modify a DLL inside that store file:
Decompress the file using decompress-assemblies utility
Patch the DLL file needed
Read the descriptor index for the compressed DLL, to get that, use assembly-store-reader, then write down the offset of the assembly that requires patching, open assemblies.blob in a hex editor, navigate to the offset of the DLL, descriptor index will be inside the uint after the compression magic uint (XALZ)
Compress the DLL file again using AssemblyCompression class: and use the descriptor index as an input to AssemblyData constructor
Add this method to AssemblyStoreReader to recreate the assemblies.blob with the patched DLL:
internal void SaveStoreToStream(Stream output)
{
EnsureStoreDataAvailable();
// Load patched DLL.
MemoryStream dllStream;
using (var fs = File.Open("/location/of/patched.dll.lz4", FileMode.Open, FileAccess.Read))
{
dllStream = new MemoryStream();
fs.CopyTo(dllStream);
}
// Index of the assembly to patch in assemblies.blob file.
var patchedAssemblyIndex = 17;
uint offsetDiff = (uint)(Assemblies[patchedAssemblyIndex].DataSize - dllStream.Length);
using (var bw = new BinaryWriter(output))
{
bw.Write(ASSEMBLY_STORE_MAGIC);
bw.Write(ASSEMBLY_STORE_FORMAT_VERSION);
bw.Write(LocalEntryCount);
bw.Write(GlobalEntryCount);
bw.Write(StoreID);
foreach (AssemblyStoreAssembly assembly in Assemblies)
{
if (assembly.RuntimeIndex > patchedAssemblyIndex)
{
bw.Write(assembly.DataOffset - offsetDiff);
} else
{
bw.Write(assembly.DataOffset);
}
if (assembly.RuntimeIndex == patchedAssemblyIndex)
{
bw.Write((uint)dllStream.Length);
} else
{
bw.Write(assembly.DataSize);
}
bw.Write(assembly.DebugDataOffset);
bw.Write(assembly.DebugDataSize);
bw.Write(assembly.ConfigDataOffset);
bw.Write(assembly.ConfigDataSize);
}
foreach (AssemblyStoreHashEntry entry in GlobalIndex32)
{
bw.Write(entry.Hash);
bw.Write(entry.MappingIndex);
bw.Write(entry.LocalStoreIndex);
bw.Write(entry.StoreID);
}
foreach (AssemblyStoreHashEntry entry in GlobalIndex64)
{
bw.Write(entry.Hash);
bw.Write(entry.MappingIndex);
bw.Write(entry.LocalStoreIndex);
bw.Write(entry.StoreID);
}
foreach (AssemblyStoreAssembly assembly in Assemblies) {
if (assembly.RuntimeIndex == patchedAssemblyIndex)
{
bw.Write(dllStream.ToArray());
}
else
{
using (var stream = new MemoryStream())
{
assembly.ExtractImage(stream);
bw.Write(stream.ToArray());
}
}
}
}
}
Write the stream passed to the above method to a new file and that will be the assemblies.blob with the patched DLL
Use apktool to recreate the APK, align and sign, then it should be working with your patched DLL

Related

Saxon CS: transform.doTransform cannot find out file from first transformation on windows machine but can on mac

I am creating an azure function application to validate xml files using a zip folder of schematron files.
I have run into a compatibility issue with how the URI's for the files are being created between mac and windows.
The files are downloaded from a zip on azure blob storage and then extracted to the functions local storage.
When the a colleague runs the transform method of the saxon cs api on a windows machine the method is able to run the first transformation and produce the stage 1.out file, however on the second transformation the transform method throws an exception stating that it cannot find the file even though it is present on the temp directory.
On mac the URI is /var/folders/6_/3x594vpn6z1fjclc0vx4v89m0000gn/T and on windows it is trying to find it at file:///C:/Users/44741/AppData/Local/Temp/ but the library is unable to find the file on the windows machine even if it is moved out of temp storage.
Unable to retrieve URI file:///C:/Users/44741/Desktop/files/stage1.out
The file is present at this location but for some reason the library cannot pick it up on the windows machine but it works fine on my mac. I am using Path.Combine to build the URI.
Has anyone else ran into this issue before?
The code being used for the transformations is below.
{
try
{
var transform = new Transform();
transform.doTransform(GetTransformArguments(arguments[Constants.InStage1File],
arguments[Constants.SourceDir] + "/" + schematronFile, arguments[Constants.Stage1Out]));
transform.doTransform(GetTransformArguments(arguments[Constants.InStage2File], arguments[Constants.Stage1Out],
arguments[Constants.Stage2Out]));
transform.doTransform(GetFinalTransformArguments(arguments[Constants.InStage3File], arguments[Constants.Stage2Out],
arguments[Constants.Stage3Out]));
Log.Information("Stage 3 out file written to : " + arguments[Constants.Stage3Out]);;
return true;
}
catch (FileNotFoundException ex)
{
Log.Warning("Cannot find files" + ex);
return false;
}
}
private static string[] GetTransformArguments(string xslFile, string inputFile, string outputFile)
{
return new[]
{
"-xsl:" + xslFile,
"-s:" + inputFile,
"-o:" + outputFile
};
}
private static string[] GetFinalTransformArguments(string xslFile, string inputFile, string outputFile)
{
return new[]
{
"-xsl:" + xslFile,
"-s:" + inputFile,
"-o:" + outputFile,
"allow-foreign=true",
"generate-fired-rule=true"
};
}```
So assuming the intermediary results are not needed as files but you just want the result (I assume that is the Schematron schema compiled to XSLT) you could try to run XSLT 3.0 using the API of SaxonCS (using Saxon.Api) by compiling and chaining your three stylesheets with e.g.
using Saxon.Api;
string isoSchematronDir = #"C:\SomePath\SomeDir\iso-schematron-xslt2";
string[] isoSchematronXslts = { "iso_dsdl_include.xsl", "iso_abstract_expand.xsl", "iso_svrl_for_xslt2.xsl" };
Processor processor = new(true);
var xsltCompiler = processor.NewXsltCompiler();
var baseUri = new Uri(Path.Combine(isoSchematronDir, isoSchematronXslts[2]));
xsltCompiler.BaseUri = baseUri;
var isoSchematronStages = isoSchematronXslts.Select(xslt => xsltCompiler.Compile(new Uri(baseUri, xslt)).Load30()).ToList();
isoSchematronStages[2].SetStylesheetParameters(new Dictionary<QName, XdmValue>() { { new QName("allow-foreign"), new XdmAtomicValue(true) } });
using (var schematronIs = File.OpenRead("price.sch"))
{
using (var compiledOs = File.OpenWrite("price.sch.xsl"))
{
isoSchematronStages[0].ApplyTemplates(
schematronIs,
isoSchematronStages[1].AsDocumentDestination(
isoSchematronStages[2].AsDocumentDestination(processor.NewSerializer(compiledOs)
)
);
}
}
If you only need the compiled Schematron to apply it further to validate an XML instance document against that Schematron you could even store the Schematron as an XdmDestination whose XdmNode you feed to XsltCompiler e.g.
using Saxon.Api;
string isoSchematronDir = #"C:\SomePath\SomeDir\iso-schematron-xslt2";
string[] isoSchematronXslts = { "iso_dsdl_include.xsl", "iso_abstract_expand.xsl", "iso_svrl_for_xslt2.xsl" };
Processor processor = new(true);
var xsltCompiler = processor.NewXsltCompiler();
var baseUri = new Uri(Path.Combine(isoSchematronDir, isoSchematronXslts[2]));
xsltCompiler.BaseUri = baseUri;
var isoSchematronStages = isoSchematronXslts.Select(xslt => xsltCompiler.Compile(new Uri(baseUri, xslt)).Load30()).ToList();
isoSchematronStages[2].SetStylesheetParameters(new Dictionary<QName, XdmValue>() { { new QName("allow-foreign"), new XdmAtomicValue(true) } });
var compiledSchematronXslt = new XdmDestination();
using (var schematronIs = File.OpenRead("price.sch"))
{
isoSchematronStages[0].ApplyTemplates(
schematronIs,
isoSchematronStages[1].AsDocumentDestination(
isoSchematronStages[2].AsDocumentDestination(compiledSchematronXslt)
)
);
}
var schematronValidator = xsltCompiler.Compile(compiledSchematronXslt.XdmNode).Load30();
using (var sampleIs = File.OpenRead("books.xml"))
{
schematronValidator.ApplyTemplates(sampleIs, processor.NewSerializer(Console.Out));
}
The last example writes the XSLT/Schematron validation SVRL output to the console but could of course also write it to a file.

Copy database outside APK

I am trying to copy database from Asset folder , but ufortunetly i've got the errror: System.UnauthorizedAccessException: 'Access to the path "/storage/emulated/0/Northwind.sqlite" is denied.' I added Runtime Permission. Could you tell me what am i doing wrong? Below is my source code:
string dbName = "Northwind.sqlite";
string dbPath = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.ToString(), dbName);
// Check if your DB has already been extracted.
if (!File.Exists(dbPath))
{
using (BinaryReader br = new BinaryReader(Android.App.Application.Context.Assets.Open(dbName)))
{
using (BinaryWriter bw = new BinaryWriter(new FileStream(dbPath, FileMode.Create)))
{
byte[] buffer = new byte[2048];
int len = 0;
while ((len = br.Read(buffer, 0, buffer.Length)) > 0)
{
bw.Write(buffer, 0, len);
}
}
}
}
You could follow the stpes below. It works well on my side.
My database in Assets folder.
Set the Build Action as AndroidAssect.
You could use the following code to copy the file from Assects folder to Android Application folder
// Android application default folder.
var dbFile = GetDefaultFolderPath();
// Check if the file already exists.
if (!File.Exists(dbFile))
{
using (FileStream writeStream = new FileStream(dbFile, FileMode.OpenOrCreate, FileAccess.Write))
{
// Assets is comming from the current context.
await Assets.Open(databaseName).CopyToAsync(writeStream);
}
}
Download the source file from the link below.
https://github.com/damienaicheh/CopyAssetsProject

How do I increase the size of an Azure File Storage CloudFile before I know the file size?

I'm using Azure File Storage to store some files, and I want to create a zip file containing some of these files on the same Azure file share.
This is my code so far:
private void CreateZip(CloudFileDirectory directory) {
if (directory == null) throw new ArgumentNullException(nameof(directory));
var zipFilename = $"{directory.Name}.zip";
var zip = directory.GetFileReference(zipFilename);
if (!zip.Exists()) {
zip.Create(0); // <-- I don't know what size its gonna be!!
using (var zipStream = zip.OpenWrite(null))
using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create)) {
foreach (var file in directory.ListFilesAndDirectories().OfType<CloudFile>()) {
if (file.Name.Equals(zipFilename, StringComparison.InvariantCultureIgnoreCase))
continue;
using (var fileStream = file.OpenRead()) {
var entry = archive.CreateEntry(file.Name);
using (var entryStream = entry.Open())
fileStream.CopyTo(entryStream); // <-- exception is thrown
}
}
}
}
}
On the line zip.Create(0); this creates an empty file. I then go on to use this file reference to create a zip file, and add stuff to it, but when it gets to the fileStream.CopyTo(entryStream); it throws an exception with this message:
The remote server returned an error: (416) The range specified is invalid for the current size of the resource.
Presumably because the file size is 0 and it's unable to automatically increase the size.
I can create the file with int.MaxValue, but then I get a 2GB file. I can't even work out the size of the file I'm adding to the achive and resize the file to extend it by that amount, because its a zip and its gonna compress and change the file size.
How do I do this?
This issue is more related with System.IO.Compression. I have rewrite your code, please use memory stream instead like the following code. It works fine on my side. Hope it could give you some tips.
public static void CreateZip(CloudFileDirectory directory)
{
if (directory == null) throw new ArgumentNullException(nameof(directory));
var zipFilename = $"{directory.Name}.zip";
var zip = directory.GetFileReference(zipFilename);
if (!zip.Exists())
{
//zip.Create(600000); // <-- I don't know what size its gonna be!!
using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach (var file in directory.ListFilesAndDirectories().OfType<CloudFile>())
{
if (file.Name.Equals(zipFilename, StringComparison.InvariantCultureIgnoreCase))
continue;
using (var fileStream = file.OpenRead())
{
var entry = archive.CreateEntry(file.Name, CompressionLevel.Optimal);
using (var entryStream = entry.Open())
{
fileStream.CopyTo(entryStream); // <-- exception is thrown
}
}
}
}
memoryStream.Seek(0, SeekOrigin.Begin);
zip.UploadFromStream(memoryStream);
}
}
}

XNA XBOX highscore port

I'm trying to port my pc XNA game to the xbox and have tried to implement xna easystorage alongside my existing pc file management for highscores. Basically trying to combine http://xnaessentials.com/tutorials/highscores.aspx/tutorials/highscores.aspx with http://easystorage.codeplex.com/
I'm running into one specific error regarding the LoadHighScores() as error with 'return (data);' - Use of unassigned local variable 'data'.
I presume this is due to async design of easystorage/xbox!? but not sure how to resolve - below are code samples:
ORIGINAL PC CODE: (works on PC)
public static HighScoreData LoadHighScores(string filename)
{
HighScoreData data; // Get the path of the save game
string fullpath = "Content/highscores.lst";
// Open the file
FileStream stream = File.Open(fullpath, FileMode.Open,FileAccess.Read);
try
{ // Read the data from the file
XmlSerializer serializer = new XmlSerializer(typeof(HighScoreData));
data = (HighScoreData)serializer.Deserialize(stream);
}
finally
{ // Close the file
stream.Close();
}
return (data);
}
XBOX PORT: (with error)
public static HighScoreData LoadHighScores(string container, string filename)
{
HighScoreData data;
if (Global.SaveDevice.FileExists(container, filename))
{
Global.SaveDevice.Load(container, filename, stream =>
{
File.Open(Global.fileName_options, FileMode.Open,//FileMode.OpenOrCreate,
FileAccess.Read);
try
{
// Read the data from the file
XmlSerializer serializer = new XmlSerializer(typeof(HighScoreData));
data = (HighScoreData)serializer.Deserialize(stream);
}
finally
{
// Close the file
stream.Close();
}
});
}
return (data);
}
Any ideas?
Assign data before return. ;)
data = (if_struct) ? new your_struct() : null;
if (Global.SaveDevice.FileExists(container, filename))
{
......
}
return (data);
}

How to retrieve data from a attached zip file in Blackberry application?

I am using eclipse to build application for Blackberry. I attached a zip file with my application. Please help me, I don't know how to retrieve data form the zip file in application development.
In BlackBerry we can use two compression standarts: GZip and ZLib.
Choose one, then compress your file and add to project.
Then you should be able to open it as an resource.
After that decompress it with GZIPInputStream or ZLibInputStream accordingly.
Example (uncompress and print text from test.gz attached to project):
try
{
InputStream inputStream = getClass().getResourceAsStream("test.gz");
GZIPInputStream gzis = new GZIPInputStream(inputStream);
StringBuffer sb = new StringBuffer();
int i;
while ((i = gzis.read()) != -1)
{
sb.append((char)i);
}
String data = sb.toString();
add(new RichTextField(data));
gzis.close();
}
catch(IOException ioe)
{
//do something here
}

Resources