Skip to content

Commit 00f37e0

Browse files
sunnamed434claude
andcommitted
Add full read/write BAML codec; use it for WPF exclusion
Replace the read-only seek-skip BAML reader with a complete BAML record model (read + write) and a BamlReader/BamlWriter that round-trip byte-for-byte (verified on real WPF .g.resources: mainwindow.baml 918B and mycontrol.baml 684B re-serialize identically). The WPF reference resolver now drives off this codec, and it's the foundation for rewriting BAML (rename XAML-referenced types and update the BAML to match) rather than only excluding them. WPF exclusion still verified end-to-end; full solution builds, all 160 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 3f69f9f commit 00f37e0

5 files changed

Lines changed: 1090 additions & 232 deletions

File tree

src/BitMono.Core/Analyzing/Baml/BamlBinaryReader.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,35 @@ public int ReadEncodedInt()
3232
return value;
3333
}
3434
}
35+
36+
/// <summary>
37+
/// <see cref="BinaryWriter"/> counterpart of <see cref="BamlBinaryReader"/>, used when writing BAML
38+
/// back out. <see cref="BinaryWriter.Write(string)"/> already emits the 7-bit-length-prefixed UTF-8
39+
/// strings BAML uses; this adds the 7-bit-encoded int (record size prefix).
40+
/// </summary>
41+
internal sealed class BamlBinaryWriter : BinaryWriter
42+
{
43+
public BamlBinaryWriter(Stream stream) : base(stream)
44+
{
45+
}
46+
47+
public void WriteEncodedInt(int value)
48+
{
49+
var v = (uint)value;
50+
while (v >= 0x80)
51+
{
52+
Write((byte)(v | 0x80));
53+
v >>= 7;
54+
}
55+
Write((byte)v);
56+
}
57+
58+
public static int SizeofEncodedInt(int value)
59+
{
60+
if ((value & ~0x7F) == 0) return 1;
61+
if ((value & ~0x3FFF) == 0) return 2;
62+
if ((value & ~0x1FFFFF) == 0) return 3;
63+
if ((value & ~0xFFFFFFF) == 0) return 4;
64+
return 5;
65+
}
66+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
using System.IO;
2+
using System.Text;
3+
4+
namespace BitMono.Core.Analyzing.Baml;
5+
6+
internal sealed class BamlDocument : List<BamlRecord>
7+
{
8+
public struct BamlVersion
9+
{
10+
public ushort Major;
11+
public ushort Minor;
12+
}
13+
14+
public string Signature { get; set; }
15+
public BamlVersion ReaderVersion;
16+
public BamlVersion UpdaterVersion;
17+
public BamlVersion WriterVersion;
18+
public string DocumentName { get; set; }
19+
}
20+
21+
internal static class BamlReader
22+
{
23+
public static BamlDocument ReadDocument(Stream stream)
24+
{
25+
var document = new BamlDocument();
26+
var reader = new BamlBinaryReader(stream);
27+
{
28+
var unicode = new BinaryReader(stream, Encoding.Unicode);
29+
var length = unicode.ReadUInt32();
30+
document.Signature = new string(unicode.ReadChars((int)(length >> 1)));
31+
unicode.ReadBytes((int)(((length + 3) & ~3) - length));
32+
}
33+
if (document.Signature != "MSBAML")
34+
{
35+
throw new NotSupportedException("Not a BAML stream.");
36+
}
37+
document.ReaderVersion = new BamlDocument.BamlVersion { Major = reader.ReadUInt16(), Minor = reader.ReadUInt16() };
38+
document.UpdaterVersion = new BamlDocument.BamlVersion { Major = reader.ReadUInt16(), Minor = reader.ReadUInt16() };
39+
document.WriterVersion = new BamlDocument.BamlVersion { Major = reader.ReadUInt16(), Minor = reader.ReadUInt16() };
40+
41+
var records = new Dictionary<long, BamlRecord>();
42+
while (stream.Position < stream.Length)
43+
{
44+
var position = stream.Position;
45+
var type = (BamlRecordType)reader.ReadByte();
46+
var record = Create(type);
47+
record.Position = position;
48+
record.Read(reader);
49+
document.Add(record);
50+
records[position] = record;
51+
}
52+
for (var i = 0; i < document.Count; i++)
53+
{
54+
if (document[i] is IBamlDeferRecord defer)
55+
{
56+
defer.ReadDefer(document, i, p => records[p]);
57+
}
58+
}
59+
return document;
60+
}
61+
62+
private static BamlRecord Create(BamlRecordType type)
63+
{
64+
return type switch
65+
{
66+
BamlRecordType.AssemblyInfo => new AssemblyInfoRecord(),
67+
BamlRecordType.AttributeInfo => new AttributeInfoRecord(),
68+
BamlRecordType.ConstructorParametersStart => new ConstructorParametersStartRecord(),
69+
BamlRecordType.ConstructorParametersEnd => new ConstructorParametersEndRecord(),
70+
BamlRecordType.ConstructorParameterType => new ConstructorParameterTypeRecord(),
71+
BamlRecordType.ConnectionId => new ConnectionIdRecord(),
72+
BamlRecordType.ContentProperty => new ContentPropertyRecord(),
73+
BamlRecordType.DefAttribute => new DefAttributeRecord(),
74+
BamlRecordType.DefAttributeKeyString => new DefAttributeKeyStringRecord(),
75+
BamlRecordType.DefAttributeKeyType => new DefAttributeKeyTypeRecord(),
76+
BamlRecordType.DeferableContentStart => new DeferableContentStartRecord(),
77+
BamlRecordType.DocumentEnd => new DocumentEndRecord(),
78+
BamlRecordType.DocumentStart => new DocumentStartRecord(),
79+
BamlRecordType.ElementEnd => new ElementEndRecord(),
80+
BamlRecordType.ElementStart => new ElementStartRecord(),
81+
BamlRecordType.KeyElementEnd => new KeyElementEndRecord(),
82+
BamlRecordType.KeyElementStart => new KeyElementStartRecord(),
83+
BamlRecordType.LineNumberAndPosition => new LineNumberAndPositionRecord(),
84+
BamlRecordType.LinePosition => new LinePositionRecord(),
85+
BamlRecordType.LiteralContent => new LiteralContentRecord(),
86+
BamlRecordType.NamedElementStart => new NamedElementStartRecord(),
87+
BamlRecordType.OptimizedStaticResource => new OptimizedStaticResourceRecord(),
88+
BamlRecordType.PIMapping => new PIMappingRecord(),
89+
BamlRecordType.PresentationOptionsAttribute => new PresentationOptionsAttributeRecord(),
90+
BamlRecordType.Property => new PropertyRecord(),
91+
BamlRecordType.PropertyArrayEnd => new PropertyArrayEndRecord(),
92+
BamlRecordType.PropertyArrayStart => new PropertyArrayStartRecord(),
93+
BamlRecordType.PropertyComplexEnd => new PropertyComplexEndRecord(),
94+
BamlRecordType.PropertyComplexStart => new PropertyComplexStartRecord(),
95+
BamlRecordType.PropertyCustom => new PropertyCustomRecord(),
96+
BamlRecordType.PropertyDictionaryEnd => new PropertyDictionaryEndRecord(),
97+
BamlRecordType.PropertyDictionaryStart => new PropertyDictionaryStartRecord(),
98+
BamlRecordType.PropertyListEnd => new PropertyListEndRecord(),
99+
BamlRecordType.PropertyListStart => new PropertyListStartRecord(),
100+
BamlRecordType.PropertyStringReference => new PropertyStringReferenceRecord(),
101+
BamlRecordType.PropertyTypeReference => new PropertyTypeReferenceRecord(),
102+
BamlRecordType.PropertyWithConverter => new PropertyWithConverterRecord(),
103+
BamlRecordType.PropertyWithExtension => new PropertyWithExtensionRecord(),
104+
BamlRecordType.PropertyWithStaticResourceId => new PropertyWithStaticResourceIdRecord(),
105+
BamlRecordType.RoutedEvent => new RoutedEventRecord(),
106+
BamlRecordType.StaticResourceEnd => new StaticResourceEndRecord(),
107+
BamlRecordType.StaticResourceId => new StaticResourceIdRecord(),
108+
BamlRecordType.StaticResourceStart => new StaticResourceStartRecord(),
109+
BamlRecordType.StringInfo => new StringInfoRecord(),
110+
BamlRecordType.Text => new TextRecord(),
111+
BamlRecordType.TextWithConverter => new TextWithConverterRecord(),
112+
BamlRecordType.TextWithId => new TextWithIdRecord(),
113+
BamlRecordType.TypeInfo => new TypeInfoRecord(),
114+
BamlRecordType.TypeSerializerInfo => new TypeSerializerInfoRecord(),
115+
BamlRecordType.XmlnsProperty => new XmlnsPropertyRecord(),
116+
_ => throw new NotSupportedException("Unsupported BAML record: " + type)
117+
};
118+
}
119+
}
120+
121+
internal static class BamlWriter
122+
{
123+
public static void WriteDocument(BamlDocument document, Stream stream)
124+
{
125+
var writer = new BamlBinaryWriter(stream);
126+
{
127+
var unicode = new BinaryWriter(stream, Encoding.Unicode);
128+
var length = document.Signature.Length * 2;
129+
unicode.Write(length);
130+
unicode.Write(document.Signature.ToCharArray());
131+
unicode.Write(new byte[((length + 3) & ~3) - length]);
132+
}
133+
writer.Write(document.ReaderVersion.Major);
134+
writer.Write(document.ReaderVersion.Minor);
135+
writer.Write(document.UpdaterVersion.Major);
136+
writer.Write(document.UpdaterVersion.Minor);
137+
writer.Write(document.WriterVersion.Major);
138+
writer.Write(document.WriterVersion.Minor);
139+
140+
var defers = new List<int>();
141+
for (var i = 0; i < document.Count; i++)
142+
{
143+
var record = document[i];
144+
record.Position = stream.Position;
145+
writer.Write((byte)record.Type);
146+
record.Write(writer);
147+
if (record is IBamlDeferRecord)
148+
{
149+
defers.Add(i);
150+
}
151+
}
152+
foreach (var i in defers)
153+
{
154+
((IBamlDeferRecord)document[i]).WriteDefer(document, i, writer);
155+
}
156+
}
157+
}

0 commit comments

Comments
 (0)