Пример #1
0
        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);
        }
Пример #2
0
 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);
     }
 }
Пример #3
0
		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())
		}
Пример #4
0
        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())
        }
Пример #5
0
        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;
            }));
        }