public void ExtractFile(string ArchiveFile, string DestDir, string DestFile = "") { if (DestFile == "") { DestFile = ArchiveFile; } if (DestDir.Length > 0 && DestDir[DestDir.Length - 1] != dsc) { DestDir += dsc; } DestFile = DestDir + DestFile; if (DestDir != "" && !Directory.Exists(System.IO.Path.GetDirectoryName(DestFile))) { Directory.CreateDirectory(System.IO.Path.GetDirectoryName(DestFile)); } int idx = zip.FindEntry(ArchiveFile, true); if (idx < 0) { return; } ICSharpCode.SharpZipLib.Zip.ZipEntry entry = zip[idx]; var tmpFile = Path.GetTempFileName(); using (Stream stream = zip.GetInputStream(entry)) { byte[] bytes = new byte[Convert.ToInt32(entry.Size)]; stream.Read(bytes, 0, bytes.Length); System.IO.File.WriteAllBytes(tmpFile, bytes); } Utils.UICopyOverwrite(tmpFile, DestFile); //if (File.Exists(DestFile)) //{ // Utils.UIDeleteFile(DestFile); //} //File.Copy(tmpFile, DestFile, true); }
public static byte[] GetZipEntry(ICSharpCode.SharpZipLib.Zip.ZipFile file, string entry, bool throwOnError) { if (file.FindEntry(entry, false) == -1) { if (throwOnError) { throw new FormatException("entry not found"); } else { return(null); } } else { ICSharpCode.SharpZipLib.Zip.ZipEntry zEntry = file.GetEntry(entry); System.IO.Stream s = file.GetInputStream(zEntry); byte[] result = new byte[zEntry.Size]; ICSharpCode.SharpZipLib.Core.StreamUtils.ReadFully(s, result); return(result); } }
private void btnExport_Click(object sender, EventArgs e) { //acquire target var sfd = new SaveFileDialog(); sfd.Filter = "XRNS (*.xrns)|*.xrns"; if (sfd.ShowDialog() != System.Windows.Forms.DialogResult.OK) return; //configuration: var outPath = sfd.FileName; string templatePath = Path.Combine(Path.GetDirectoryName(outPath), "template.xrns"); int configuredPatternLength = int.Parse(txtPatternLength.Text); //load template MemoryStream msSongXml = new MemoryStream(); var zfTemplate = new ICSharpCode.SharpZipLib.Zip.ZipFile(templatePath); { int zfSongXmlIndex = zfTemplate.FindEntry("Song.xml", true); using (var zis = zfTemplate.GetInputStream(zfTemplate.GetEntry("Song.xml"))) { byte[] buffer = new byte[4096]; // 4K is optimum ICSharpCode.SharpZipLib.Core.StreamUtils.Copy(zis, msSongXml, buffer); } } XElement templateRoot = XElement.Parse(System.Text.Encoding.UTF8.GetString(msSongXml.ToArray())); //get the pattern pool, and whack the child nodes var xPatterns = templateRoot.XPathSelectElement("//Patterns"); var xPatternPool = xPatterns.Parent; xPatterns.Remove(); var writer = new StringWriter(); writer.WriteLine("<Patterns>"); int pulse0_lastNote = -1; int pulse0_lastType = -1; int pulse1_lastNote = -1; int pulse1_lastType = -1; int tri_lastNote = -1; int noise_lastNote = -1; int patternCount = 0; int time = 0; while (time < Log.Count) { patternCount++; //begin writing pattern: open the tracks list writer.WriteLine("<Pattern>"); writer.WriteLine("<NumberOfLines>{0}</NumberOfLines>", configuredPatternLength); writer.WriteLine("<Tracks>"); //write the pulse tracks for (int TRACK = 0; TRACK < 2; TRACK++) { writer.WriteLine("<PatternTrack type=\"PatternTrack\">"); writer.WriteLine("<Lines>"); int lastNote = TRACK == 0 ? pulse0_lastNote : pulse1_lastNote; int lastType = TRACK == 0 ? pulse0_lastType : pulse1_lastType; for (int i = 0; i < configuredPatternLength; i++) { int patLine = i; int index = i + time; if (index >= Log.Count) continue; var rec = Log[index]; PulseState pulse = new PulseState(); if (TRACK == 0) pulse = rec.pulse0; if (TRACK == 1) pulse = rec.pulse1; //transform quieted notes to dead notes //blech its buggy, im tired //if (pulse.vol == 0) // pulse.en = false; bool keyoff = false, keyon = false; if (lastNote != -1 && !pulse.en) { lastNote = -1; lastType = -1; keyoff = true; } else if (lastNote != pulse.note && pulse.en) keyon = true; if (lastType != pulse.type && pulse.note != -1) keyon = true; if (pulse.en) { lastNote = pulse.note; lastType = pulse.type; } writer.WriteLine("<Line index=\"{0}\">", patLine); writer.WriteLine("<NoteColumns>"); writer.WriteLine("<NoteColumn>"); if (keyon) { writer.WriteLine("<Note>{0}</Note>", NameForNote(pulse.note)); writer.WriteLine("<Instrument>{0:X2}</Instrument>", pulse.type); } else if (keyoff) writer.WriteLine("<Note>OFF</Note>"); if(lastNote != -1) writer.WriteLine("<Volume>{0:X2}</Volume>", pulse.vol * 8); writer.WriteLine("</NoteColumn>"); writer.WriteLine("</NoteColumns>"); writer.WriteLine("</Line>"); } //close PatternTrack writer.WriteLine("</Lines>"); writer.WriteLine("</PatternTrack>"); if (TRACK == 0) { pulse0_lastNote = lastNote; pulse0_lastType = lastType; } else { pulse1_lastNote = lastNote; pulse1_lastType = lastType; } } //pulse tracks loop //triangle track generation { writer.WriteLine("<PatternTrack type=\"PatternTrack\">"); writer.WriteLine("<Lines>"); for (int i = 0; i < configuredPatternLength; i++) { int patLine = i; int index = i + time; if (index >= Log.Count) continue; var rec = Log[index]; TriangleState tri = rec.triangle; { bool keyoff = false, keyon = false; if (tri_lastNote != -1 && !tri.en) { tri_lastNote = -1; keyoff = true; } else if (tri_lastNote != tri.note && tri.en) keyon = true; if(tri.en) tri_lastNote = tri.note; writer.WriteLine("<Line index=\"{0}\">", patLine); writer.WriteLine("<NoteColumns>"); writer.WriteLine("<NoteColumn>"); if (keyon) { writer.WriteLine("<Note>{0}</Note>", NameForNote(tri.note)); writer.WriteLine("<Instrument>08</Instrument>"); } else if (keyoff) writer.WriteLine("<Note>OFF</Note>"); //no need for tons of these //if(keyon) writer.WriteLine("<Volume>80</Volume>"); writer.WriteLine("</NoteColumn>"); writer.WriteLine("</NoteColumns>"); writer.WriteLine("</Line>"); } } //close PatternTrack writer.WriteLine("</Lines>"); writer.WriteLine("</PatternTrack>"); } //noise track generation { writer.WriteLine("<PatternTrack type=\"PatternTrack\">"); writer.WriteLine("<Lines>"); for (int i = 0; i < configuredPatternLength; i++) { int patLine = i; int index = i + time; if (index >= Log.Count) continue; var rec = Log[index]; NoiseState noise = rec.noise; //transform quieted notes to dead notes //blech its buggy, im tired //if (noise.vol == 0) // noise.en = false; { bool keyoff = false, keyon = false; if (noise_lastNote != -1 && !noise.en) { noise_lastNote = -1; keyoff = true; } else if (noise_lastNote != noise.note && noise.en) keyon = true; if (noise.en) noise_lastNote = noise.note; writer.WriteLine("<Line index=\"{0}\">", patLine); writer.WriteLine("<NoteColumns>"); writer.WriteLine("<NoteColumn>"); if (keyon) { writer.WriteLine("<Note>{0}</Note>", NameForNote(noise.note)); writer.WriteLine("<Instrument>04</Instrument>"); } else if (keyoff) writer.WriteLine("<Note>OFF</Note>"); if (noise_lastNote != -1) writer.WriteLine("<Volume>{0:X2}</Volume>", noise.vol * 8); writer.WriteLine("</NoteColumn>"); writer.WriteLine("</NoteColumns>"); writer.WriteLine("</Line>"); } } //close PatternTrack writer.WriteLine("</Lines>"); writer.WriteLine("</PatternTrack>"); } //noise track generation //write empty track for now for pcm for (int TRACK = 4; TRACK < 5; TRACK++) { writer.WriteLine("<PatternTrack type=\"PatternTrack\">"); writer.WriteLine("<Lines>"); writer.WriteLine("</Lines>"); writer.WriteLine("</PatternTrack>"); } //we definitely need a dummy master track now writer.WriteLine("<PatternMasterTrack type=\"PatternMasterTrack\">"); writer.WriteLine("</PatternMasterTrack>"); //close tracks writer.WriteLine("</Tracks>"); //close pattern writer.WriteLine("</Pattern>"); time += configuredPatternLength; } //main pattern loop writer.WriteLine("</Patterns>"); writer.Flush(); var xNewPatternList = XElement.Parse(writer.ToString()); xPatternPool.Add(xNewPatternList); //write pattern sequence writer = new StringWriter(); writer.WriteLine("<SequenceEntries>"); for (int i = 0; i < patternCount; i++) { writer.WriteLine("<SequenceEntry>"); writer.WriteLine("<IsSectionStart>false</IsSectionStart>"); writer.WriteLine("<Pattern>{0}</Pattern>", i); writer.WriteLine("</SequenceEntry>"); } writer.WriteLine("</SequenceEntries>"); var xPatternSequence = templateRoot.XPathSelectElement("//PatternSequence"); xPatternSequence.XPathSelectElement("SequenceEntries").Remove(); xPatternSequence.Add(XElement.Parse(writer.ToString())); //copy template file to target File.Delete(outPath); File.Copy(templatePath, outPath); var msOutXml = new MemoryStream(); templateRoot.Save(msOutXml); msOutXml.Flush(); msOutXml.Position = 0; var zfOutput = new ICSharpCode.SharpZipLib.Zip.ZipFile(outPath); zfOutput.BeginUpdate(); zfOutput.Add(new Stupid { stream = msOutXml }, "Song.xml"); zfOutput.CommitUpdate(); zfOutput.Close(); //for easier debugging, write patterndata XML //DUMP_TO_DISK(msOutXml.ToArray()) }
private void btnExport_Click(object sender, EventArgs e) { //acquire target var sfd = new SaveFileDialog(); sfd.Filter = "XRNS (*.xrns)|*.xrns"; if (sfd.ShowDialog() != System.Windows.Forms.DialogResult.OK) { return; } //configuration: var outPath = sfd.FileName; string templatePath = Path.Combine(Path.GetDirectoryName(outPath), "template.xrns"); int configuredPatternLength = int.Parse(txtPatternLength.Text); //load template MemoryStream msSongXml = new MemoryStream(); var zfTemplate = new ICSharpCode.SharpZipLib.Zip.ZipFile(templatePath); { int zfSongXmlIndex = zfTemplate.FindEntry("Song.xml", true); using (var zis = zfTemplate.GetInputStream(zfTemplate.GetEntry("Song.xml"))) { byte[] buffer = new byte[4096]; // 4K is optimum ICSharpCode.SharpZipLib.Core.StreamUtils.Copy(zis, msSongXml, buffer); } } XElement templateRoot = XElement.Parse(System.Text.Encoding.UTF8.GetString(msSongXml.ToArray())); //get the pattern pool, and whack the child nodes var xPatterns = templateRoot.XPathSelectElement("//Patterns"); var xPatternPool = xPatterns.Parent; xPatterns.Remove(); var writer = new StringWriter(); writer.WriteLine("<Patterns>"); int pulse0_lastNote = -1; int pulse0_lastType = -1; int pulse1_lastNote = -1; int pulse1_lastType = -1; int tri_lastNote = -1; int noise_lastNote = -1; int patternCount = 0; int time = 0; while (time < Log.Count) { patternCount++; //begin writing pattern: open the tracks list writer.WriteLine("<Pattern>"); writer.WriteLine("<NumberOfLines>{0}</NumberOfLines>", configuredPatternLength); writer.WriteLine("<Tracks>"); //write the pulse tracks for (int TRACK = 0; TRACK < 2; TRACK++) { writer.WriteLine("<PatternTrack type=\"PatternTrack\">"); writer.WriteLine("<Lines>"); int lastNote = TRACK == 0 ? pulse0_lastNote : pulse1_lastNote; int lastType = TRACK == 0 ? pulse0_lastType : pulse1_lastType; for (int i = 0; i < configuredPatternLength; i++) { int patLine = i; int index = i + time; if (index >= Log.Count) { continue; } var rec = Log[index]; PulseState pulse = new PulseState(); if (TRACK == 0) { pulse = rec.pulse0; } if (TRACK == 1) { pulse = rec.pulse1; } //transform quieted notes to dead notes //blech its buggy, im tired //if (pulse.vol == 0) // pulse.en = false; bool keyoff = false, keyon = false; if (lastNote != -1 && !pulse.en) { lastNote = -1; lastType = -1; keyoff = true; } else if (lastNote != pulse.note && pulse.en) { keyon = true; } if (lastType != pulse.type && pulse.note != -1) { keyon = true; } if (pulse.en) { lastNote = pulse.note; lastType = pulse.type; } writer.WriteLine("<Line index=\"{0}\">", patLine); writer.WriteLine("<NoteColumns>"); writer.WriteLine("<NoteColumn>"); if (keyon) { writer.WriteLine("<Note>{0}</Note>", NameForNote(pulse.note)); writer.WriteLine("<Instrument>{0:X2}</Instrument>", pulse.type); } else if (keyoff) { writer.WriteLine("<Note>OFF</Note>"); } if (lastNote != -1) { writer.WriteLine("<Volume>{0:X2}</Volume>", pulse.vol * 8); } writer.WriteLine("</NoteColumn>"); writer.WriteLine("</NoteColumns>"); writer.WriteLine("</Line>"); } //close PatternTrack writer.WriteLine("</Lines>"); writer.WriteLine("</PatternTrack>"); if (TRACK == 0) { pulse0_lastNote = lastNote; pulse0_lastType = lastType; } else { pulse1_lastNote = lastNote; pulse1_lastType = lastType; } } //pulse tracks loop //triangle track generation { writer.WriteLine("<PatternTrack type=\"PatternTrack\">"); writer.WriteLine("<Lines>"); for (int i = 0; i < configuredPatternLength; i++) { int patLine = i; int index = i + time; if (index >= Log.Count) { continue; } var rec = Log[index]; TriangleState tri = rec.triangle; { bool keyoff = false, keyon = false; if (tri_lastNote != -1 && !tri.en) { tri_lastNote = -1; keyoff = true; } else if (tri_lastNote != tri.note && tri.en) { keyon = true; } if (tri.en) { tri_lastNote = tri.note; } writer.WriteLine("<Line index=\"{0}\">", patLine); writer.WriteLine("<NoteColumns>"); writer.WriteLine("<NoteColumn>"); if (keyon) { writer.WriteLine("<Note>{0}</Note>", NameForNote(tri.note)); writer.WriteLine("<Instrument>08</Instrument>"); } else if (keyoff) { writer.WriteLine("<Note>OFF</Note>"); } //no need for tons of these //if(keyon) writer.WriteLine("<Volume>80</Volume>"); writer.WriteLine("</NoteColumn>"); writer.WriteLine("</NoteColumns>"); writer.WriteLine("</Line>"); } } //close PatternTrack writer.WriteLine("</Lines>"); writer.WriteLine("</PatternTrack>"); } //noise track generation { writer.WriteLine("<PatternTrack type=\"PatternTrack\">"); writer.WriteLine("<Lines>"); for (int i = 0; i < configuredPatternLength; i++) { int patLine = i; int index = i + time; if (index >= Log.Count) { continue; } var rec = Log[index]; NoiseState noise = rec.noise; //transform quieted notes to dead notes //blech its buggy, im tired //if (noise.vol == 0) // noise.en = false; { bool keyoff = false, keyon = false; if (noise_lastNote != -1 && !noise.en) { noise_lastNote = -1; keyoff = true; } else if (noise_lastNote != noise.note && noise.en) { keyon = true; } if (noise.en) { noise_lastNote = noise.note; } writer.WriteLine("<Line index=\"{0}\">", patLine); writer.WriteLine("<NoteColumns>"); writer.WriteLine("<NoteColumn>"); if (keyon) { writer.WriteLine("<Note>{0}</Note>", NameForNote(noise.note)); writer.WriteLine("<Instrument>04</Instrument>"); } else if (keyoff) { writer.WriteLine("<Note>OFF</Note>"); } if (noise_lastNote != -1) { writer.WriteLine("<Volume>{0:X2}</Volume>", noise.vol * 8); } writer.WriteLine("</NoteColumn>"); writer.WriteLine("</NoteColumns>"); writer.WriteLine("</Line>"); } } //close PatternTrack writer.WriteLine("</Lines>"); writer.WriteLine("</PatternTrack>"); } //noise track generation //write empty track for now for pcm for (int TRACK = 4; TRACK < 5; TRACK++) { writer.WriteLine("<PatternTrack type=\"PatternTrack\">"); writer.WriteLine("<Lines>"); writer.WriteLine("</Lines>"); writer.WriteLine("</PatternTrack>"); } //we definitely need a dummy master track now writer.WriteLine("<PatternMasterTrack type=\"PatternMasterTrack\">"); writer.WriteLine("</PatternMasterTrack>"); //close tracks writer.WriteLine("</Tracks>"); //close pattern writer.WriteLine("</Pattern>"); time += configuredPatternLength; } //main pattern loop writer.WriteLine("</Patterns>"); writer.Flush(); var xNewPatternList = XElement.Parse(writer.ToString()); xPatternPool.Add(xNewPatternList); //write pattern sequence writer = new StringWriter(); writer.WriteLine("<SequenceEntries>"); for (int i = 0; i < patternCount; i++) { writer.WriteLine("<SequenceEntry>"); writer.WriteLine("<IsSectionStart>false</IsSectionStart>"); writer.WriteLine("<Pattern>{0}</Pattern>", i); writer.WriteLine("</SequenceEntry>"); } writer.WriteLine("</SequenceEntries>"); var xPatternSequence = templateRoot.XPathSelectElement("//PatternSequence"); xPatternSequence.XPathSelectElement("SequenceEntries").Remove(); xPatternSequence.Add(XElement.Parse(writer.ToString())); //copy template file to target File.Delete(outPath); File.Copy(templatePath, outPath); var msOutXml = new MemoryStream(); templateRoot.Save(msOutXml); msOutXml.Flush(); msOutXml.Position = 0; var zfOutput = new ICSharpCode.SharpZipLib.Zip.ZipFile(outPath); zfOutput.BeginUpdate(); zfOutput.Add(new Stupid { stream = msOutXml }, "Song.xml"); zfOutput.CommitUpdate(); zfOutput.Close(); //for easier debugging, write patterndata XML //DUMP_TO_DISK(msOutXml.ToArray()) }
public static async Task <ClassBuilder?> ProcessFileAsync(Options options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } var fileName = options.ApplicationTouchpanelPath; return(await Task.Run(async() => { TouchpanelCore = new ClassBuilder(ClassType.Touchpanel); if ((!fileName.EndsWith(".vtz", StringComparison.OrdinalIgnoreCase) && !fileName.EndsWith("Environment.xml", StringComparison.OrdinalIgnoreCase) && !fileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase) && !fileName.EndsWith(".c3p", StringComparison.OrdinalIgnoreCase) ) || !System.IO.File.Exists(fileName)) { return null; } var contents = ""; if (fileName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)) { contents = System.IO.File.ReadAllText(fileName); } else { var zip = new ICSharpCode.SharpZipLib.Zip.ZipFile(fileName); if (zip.FindEntry("swf/Environment.xml", true) > -1) { var entry = zip.GetEntry("swf/Environment.xml"); using (var reader = new System.IO.StreamReader(zip.GetInputStream(entry))) { contents = await reader.ReadToEndAsync(); } } else if (zip.FindEntry("Environment.xml", true) > -1) { var entry = zip.GetEntry("Environment.xml"); using (var reader = new System.IO.StreamReader(zip.GetInputStream(entry))) { contents = await reader.ReadToEndAsync(); } } } if (string.IsNullOrEmpty(contents)) { return null; } var doc = XDocument.Parse(contents); TouchpanelCore.ClassName = doc?.Root?.Element("ObjectName")?.Value ?? ""; TouchpanelCore.Namespace = options.RootNamespace; options.PanelNamespace = $"{options.RootNamespace}.{TouchpanelCore.ClassName}"; // Projects potentially have a theme join. GenericParser.ParseTheme(doc?.Root?.Element("Properties"), TouchpanelCore); // Try to parse for background joins as well. GenericParser.ParseBackgroundJoins(doc?.Root?.Element("Properties"), TouchpanelCore); // Then we have to determine if there are hardkeys in play. if (options.ParseHardkeys) { if (int.TryParse(doc?.Root?.Element("Properties")?.Element("Hardkeys")?.Element("NumHardkeys")?.Value, out var count) && count > 0) { var hardkeys = from hk in doc?.Root?.Element("Properties")?.Element("Hardkeys")?.Descendants() where hk?.Name == "Hardkey" select hk; if (hardkeys.Any()) { foreach (var hk in hardkeys) { var key = HardkeyParser.ParseElement(hk, options); if (key != null) { TouchpanelCore.AddJoin(key); } } } } } var pages = from p in doc?.Root?.Element("Properties")?.Element("Pages")?.Descendants() where p?.Element("TargetControl")?.Value == "Page" select p; var subpages = from sp in doc?.Root?.Element("Properties")?.Element("Pages")?.Descendants() where sp?.Element("TargetControl")?.Value == "Subpage" select sp; foreach (var page in pages) { var pageBuilder = new ClassBuilder(ClassType.Page) { ClassName = page?.Element("ObjectName")?.Value ?? "", NamespaceBase = $"{options.PanelNamespace}.Components" }; if (pageBuilder.ClassName == "Subpage Reference" || pageBuilder.ClassName == "Page") { pageBuilder.ClassName = page?.Attribute("Name")?.Value ?? ""; } if (string.IsNullOrEmpty(pageBuilder.ClassName)) { throw new NullReferenceException("Unable to determine a name for a page or subpage reference."); } var props = page?.Element("Properties"); if (ushort.TryParse(props?.Element("DigitalJoin").Value, out var pageJoin) && pageJoin > 0) { pageBuilder.AddJoin(new JoinBuilder(pageJoin, pageBuilder.SmartJoin, "IsVisible", JoinType.Digital, JoinDirection.ToPanel)); } if (props != null) { pageBuilder.DigitalOffset = GenericParser.ParseDigitalOffset(props); pageBuilder.AnalogOffset = GenericParser.ParseAnalogOffset(props); pageBuilder.SerialOffset = GenericParser.ParseSerialOffset(props); GenericParser.ParseBackgroundJoins(props, pageBuilder); var transitionJoin = GenericParser.GetTransitionCompleteJoin(props); if (transitionJoin != null) { pageBuilder.AddJoin(transitionJoin); } } var pageChildren = props?.Element("Children")?.Elements()?.Where(e => e?.Name.LocalName != "Subpage"); if (pageChildren != null) { foreach (var c in pageChildren) { GenericParser.ParseChildElement(c, pageBuilder); } } var subpageCount = props?.Element("Children")?.Elements().Where(e => e.Name == "Subpage").Count(); if (subpageCount > 0) { foreach (var sp in page?.Element("Properties")?.Element("Children")?.Elements()?.Where(e => e.Name == "Subpage") ?? Array.Empty <XElement>()) { var subElement = subpages.Where(sub => sub.Attribute("uid")?.Value == sp.Element("Properties")?.Element("PageID")?.Value).FirstOrDefault(); if (subElement == null) { continue; } var subBuilder = new ClassBuilder(ClassType.Page) { ClassName = sp?.Element("ObjectName")?.Value ?? "", Namespace = pageBuilder.Namespace }; if (subBuilder.ClassName == "Subpage Reference" || subBuilder.ClassName == "SubpageReference" || subBuilder.ClassName == "Page") { var rootSubpage = subpages.Where(s => (s.Attribute("uid")?.Value ?? "null1") == (sp?.Attribute("uid").Value ?? "null2")).FirstOrDefault(); subBuilder.ClassName = rootSubpage?.Attribute("Name")?.Value ?? ""; } if (string.IsNullOrEmpty(subBuilder.ClassName)) { throw new NullReferenceException("Unable to determine a name for a page or subpage reference."); } var subProps = sp?.Element("Properties"); if (subProps != null) { if (ushort.TryParse(subProps?.Element("DigitalJoin").Value, out var subpageJoin) && subpageJoin > 0) { subBuilder.AddJoin(new JoinBuilder(subpageJoin, subBuilder.SmartJoin, "IsVisible", JoinType.Digital, JoinDirection.ToPanel)); } subBuilder.DigitalOffset = GenericParser.ParseDigitalOffset(subProps); subBuilder.AnalogOffset = GenericParser.ParseAnalogOffset(subProps); subBuilder.SerialOffset = GenericParser.ParseSerialOffset(subProps); var transitionJoin = GenericParser.GetTransitionCompleteJoin(subProps); if (transitionJoin != null) { subBuilder.AddJoin(transitionJoin); } } subProps = subElement.Element("Properties"); if (subProps != null) { GenericParser.ParseBackgroundJoins(subProps, subBuilder); if (subProps.Element("Children")?.Elements()?.Count() > 0) { foreach (var c in subProps.Element("Children").Elements().Where(e => e.Name.LocalName != "Subpage")) { GenericParser.ParseChildElement(c, subBuilder); } } } if (subBuilder.IsValid) { pageBuilder.AddPage(subBuilder); } } } TouchpanelCore.AddPage(pageBuilder); } return TouchpanelCore; })); }