Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public AssemblyDescriptor(Assembly assembly)
Resources.Remove(Constants.AvaloniaResourceName);

var indexLength = new BinaryReader(resources).ReadInt32();
var index = AvaloniaResourcesIndexReaderWriter.Read(new SlicedStream(resources, 4, indexLength));
var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength));
var baseOffset = indexLength + 4;
AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor)
new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
Expand Down
149 changes: 80 additions & 69 deletions src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
Original file line number Diff line number Diff line change
@@ -1,105 +1,116 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Xml.Linq;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using System.Text;

namespace Avalonia.Utilities
{
#if !BUILDTASK
#if !BUILDTASK
public
#endif
#endif
static class AvaloniaResourcesIndexReaderWriter
{
private const int LastKnownVersion = 1;
public static List<AvaloniaResourcesIndexEntry> Read(Stream stream)
private const int XmlLegacyVersion = 1;
private const int BinaryCurrentVersion = 2;

public static List<AvaloniaResourcesIndexEntry> ReadIndex(Stream stream)
{
var ver = new BinaryReader(stream).ReadInt32();
if (ver > LastKnownVersion)
throw new Exception("Resources index format version is not known");

var assetDoc = XDocument.Load(stream);
XNamespace assetNs = assetDoc.Root!.Attribute("xmlns")!.Value;
List<AvaloniaResourcesIndexEntry> entries =
(from entry in assetDoc.Root.Element(assetNs + "Entries")!.Elements(assetNs + "AvaloniaResourcesIndexEntry")
select new AvaloniaResourcesIndexEntry
{
Path = entry.Element(assetNs + "Path")!.Value,
Offset = int.Parse(entry.Element(assetNs + "Offset")!.Value),
Size = int.Parse(entry.Element(assetNs + "Size")!.Value)
}).ToList();
using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);

return entries;
var version = reader.ReadInt32();
return version switch
{
XmlLegacyVersion => ReadXmlIndex(),
BinaryCurrentVersion => ReadBinaryIndex(reader),
_ => throw new Exception($"Unknown resources index format version {version}")
};
}

[RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")]
public static void Write(Stream stream, List<AvaloniaResourcesIndexEntry> entries)
private static List<AvaloniaResourcesIndexEntry> ReadXmlIndex()
=> throw new NotSupportedException("Found legacy resources index format: please recompile your XAML files");

private static List<AvaloniaResourcesIndexEntry> ReadBinaryIndex(BinaryReader reader)
{
new BinaryWriter(stream).Write(LastKnownVersion);
new DataContractSerializer(typeof(AvaloniaResourcesIndex)).WriteObject(stream,
new AvaloniaResourcesIndex()
{
Entries = entries
var entryCount = reader.ReadInt32();
var entries = new List<AvaloniaResourcesIndexEntry>(entryCount);

for (var i = 0; i < entryCount; ++i)
{
entries.Add(new AvaloniaResourcesIndexEntry {
Path = reader.ReadString(),
Offset = reader.ReadInt32(),
Size = reader.ReadInt32()
});
}

return entries;
}

[RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")]
public static byte[] Create(Dictionary<string, byte[]> data)
public static void WriteIndex(Stream output, List<AvaloniaResourcesIndexEntry> entries)
{
var sources = data.ToList();
var offsets = new Dictionary<string, int>();
var coffset = 0;
foreach (var s in sources)
using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true);

WriteIndex(writer, entries);
}

private static void WriteIndex(BinaryWriter writer, List<AvaloniaResourcesIndexEntry> entries)
{
writer.Write(BinaryCurrentVersion);
writer.Write(entries.Count);

foreach (var entry in entries)
{
offsets[s.Key] = coffset;
coffset += s.Value.Length;
writer.Write(entry.Path ?? string.Empty);
writer.Write(entry.Offset);
writer.Write(entry.Size);
}
var index = sources.Select(s => new AvaloniaResourcesIndexEntry
{
Path = s.Key,
Size = s.Value.Length,
Offset = offsets[s.Key]
}).ToList();
var output = new MemoryStream();
var ms = new MemoryStream();
AvaloniaResourcesIndexReaderWriter.Write(ms, index);
new BinaryWriter(output).Write((int)ms.Length);
ms.Position = 0;
ms.CopyTo(output);
foreach (var s in sources)
}

public static void WriteResources(Stream output, List<(string Path, int Size, Func<Stream> Open)> resources)
{
var entries = new List<AvaloniaResourcesIndexEntry>(resources.Count);
var offset = 0;

foreach (var resource in resources)
{
output.Write(s.Value,0,s.Value.Length);
entries.Add(new AvaloniaResourcesIndexEntry
{
Path = resource.Path,
Offset = offset,
Size = resource.Size
});
offset += resource.Size;
}

return output.ToArray();
}
}
using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true);
writer.Write(0); // index size placeholder, overwritten below

[DataContract]
#if !BUILDTASK
public
#endif
class AvaloniaResourcesIndex
{
[DataMember]
public List<AvaloniaResourcesIndexEntry> Entries { get; set; } = new List<AvaloniaResourcesIndexEntry>();
var posBeforeEntries = output.Position;
WriteIndex(writer, entries);

var posAfterEntries = output.Position;
var indexSize = (int) (posAfterEntries - posBeforeEntries);
output.Position = 0L;
writer.Write(indexSize);
output.Position = posAfterEntries;

foreach (var resource in resources)
{
using var resourceStream = resource.Open();
resourceStream.CopyTo(output);
}
}
}

[DataContract]
#if !BUILDTASK
public
#endif
class AvaloniaResourcesIndexEntry
{
[DataMember]
public string? Path { get; set; }

[DataMember]

public int Offset { get; set; }

[DataMember]

public int Size { get; set; }
}
}
3 changes: 3 additions & 0 deletions src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
<Compile Include="../Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs">
<Link>Shared/AvaloniaResourcesIndex.cs</Link>
</Compile>
<Compile Include="../Avalonia.Base/Platform/Internal/Constants.cs">
<Link>Shared/Constants.cs</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs">
<Link>Shared/AvaloniaResourceXamlInfo.cs</Link>
</Compile>
Expand Down
26 changes: 3 additions & 23 deletions src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,29 +77,9 @@ List<Source> BuildResourceSources()

private void Pack(Stream output, List<Source> sources)
{
var offsets = new Dictionary<Source, int>();
var coffset = 0;
foreach (var s in sources)
{
offsets[s] = coffset;
coffset += s.Size;
}
var index = sources.Select(s => new AvaloniaResourcesIndexEntry
{
Path = s.Path,
Size = s.Size,
Offset = offsets[s]
}).ToList();
var ms = new MemoryStream();
AvaloniaResourcesIndexReaderWriter.Write(ms, index);
new BinaryWriter(output).Write((int)ms.Length);
ms.Position = 0;
ms.CopyTo(output);
foreach (var s in sources)
{
using (var input = s.Open())
input.CopyTo(output);
}
AvaloniaResourcesIndexReaderWriter.WriteResources(
output,
sources.Select(source => (source.Path, source.Size, (Func<Stream>) source.Open)).ToList());
}

private bool PreProcessXamlFiles(List<Source> sources)
Expand Down
21 changes: 16 additions & 5 deletions src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Avalonia.Platform.Internal;
using Avalonia.Utilities;
using Mono.Cecil;
using Mono.Cecil.Cil;
Expand Down Expand Up @@ -34,13 +36,13 @@ public AvaloniaResources(AssemblyDefinition asm, string projectDir)
{
_asm = asm;
_embedded = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r =>
r.ResourceType == ResourceType.Embedded && r.Name == "!AvaloniaResources"));
r.ResourceType == ResourceType.Embedded && r.Name == Constants.AvaloniaResourceName));
if (_embedded == null)
return;
using (var stream = _embedded.GetResourceStream())
{
var br = new BinaryReader(stream);
var index = AvaloniaResourcesIndexReaderWriter.Read(new MemoryStream(br.ReadBytes(br.ReadInt32())));
var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new MemoryStream(br.ReadBytes(br.ReadInt32())));
var baseOffset = stream.Position;
foreach (var e in index)
{
Expand All @@ -61,9 +63,18 @@ public void Save()
if (_resources.Count == 0)
return;

_embedded = new EmbeddedResource("!AvaloniaResources", ManifestResourceAttributes.Public,
AvaloniaResourcesIndexReaderWriter.Create(_resources.ToDictionary(x => x.Key,
x => x.Value.FileContents)));
var output = new MemoryStream();

AvaloniaResourcesIndexReaderWriter.WriteResources(
output,
_resources.Select(x => (
Path: x.Key,
Size: x.Value.FileContents.Length,
Open: (Func<Stream>) (() => new MemoryStream(x.Value.FileContents))
)).ToList());

output.Position = 0L;
_embedded = new EmbeddedResource(Constants.AvaloniaResourceName, ManifestResourceAttributes.Public, output);
_asm.MainModule.Resources.Add(_embedded);
}

Expand Down