From 713c286dffadaa02503048dbbc739f0906f10192 Mon Sep 17 00:00:00 2001 From: kawaiizenbo <48113593+kawaiizenbo@users.noreply.github.com> Date: Sat, 18 Jun 2022 15:54:26 -0700 Subject: [PATCH] consider your bugs hunted --- IPASorter/FileClass.cs | 22 - IPASorter/IPASorter.csproj | 6 - IPASorter/Plist.cs | 961 +++++++++++++++++++++++++++++++++++++ IPASorter/PlistCS.dll | Bin 17920 -> 0 bytes IPASorter/Program.cs | 211 ++++---- 5 files changed, 1091 insertions(+), 109 deletions(-) delete mode 100644 IPASorter/FileClass.cs create mode 100644 IPASorter/Plist.cs delete mode 100644 IPASorter/PlistCS.dll diff --git a/IPASorter/FileClass.cs b/IPASorter/FileClass.cs deleted file mode 100644 index 6e3b624..0000000 --- a/IPASorter/FileClass.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace IPASorter -{ - public class IPAFile - { - public string path { get; set; } - public string md5sum { get; set; } - public string fileName { get; set; } - public string CFBundleIdentifier { get; set; } - public string CFBundleVersion { get; set; } - public string MinimumOSVersion { get; set; } - public string CFBundleDisplayName { get; set; } - } - - public class AppList - { - public IPAFile[] apps { get; set; } - } -} diff --git a/IPASorter/IPASorter.csproj b/IPASorter/IPASorter.csproj index 5733140..c73e0d1 100644 --- a/IPASorter/IPASorter.csproj +++ b/IPASorter/IPASorter.csproj @@ -5,10 +5,4 @@ netcoreapp3.1 - - - PlistCS.dll - - - diff --git a/IPASorter/Plist.cs b/IPASorter/Plist.cs new file mode 100644 index 0000000..f52c364 --- /dev/null +++ b/IPASorter/Plist.cs @@ -0,0 +1,961 @@ +// +// PlistCS Property List (plist) serialization and parsing library. +// +// https://github.com/animetrics/PlistCS +// +// Copyright (c) 2011 Animetrics Inc. (marc@animetrics.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; + +namespace PlistCS +{ + public static class Plist + { + private static List offsetTable = new List(); + private static List objectTable = new List(); + private static int refCount; + private static int objRefSize; + private static int offsetByteSize; + private static long offsetTableOffset; + + #region Public Functions + + public static object readPlist(string path) + { + using (FileStream f = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + return readPlist(f, plistType.Auto); + } + } + + public static object readPlistSource(string source) + { + return readPlist(System.Text.Encoding.UTF8.GetBytes(source)); + } + + public static object readPlist(byte[] data) + { + return readPlist(new MemoryStream(data), plistType.Auto); + } + + public static plistType getPlistType(Stream stream) + { + byte[] magicHeader = new byte[8]; + stream.Read(magicHeader, 0, 8); + + if (BitConverter.ToInt64(magicHeader, 0) == 3472403351741427810) + { + return plistType.Binary; + } + else + { + return plistType.Xml; + } + } + + public static object readPlist(Stream stream, plistType type) + { + if (type == plistType.Auto) + { + type = getPlistType(stream); + stream.Seek(0, SeekOrigin.Begin); + } + + if (type == plistType.Binary) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + byte[] data = reader.ReadBytes((int)reader.BaseStream.Length); + return readBinary(data); + } + } + else + { + XmlDocument xml = new XmlDocument(); + xml.XmlResolver = null; + xml.Load(stream); + return readXml(xml); + } + } + + public static void writeXml(object value, string path) + { + using (StreamWriter writer = new StreamWriter(path)) + { + writer.Write(writeXml(value)); + } + } + + public static void writeXml(object value, Stream stream) + { + using (StreamWriter writer = new StreamWriter(stream)) + { + writer.Write(writeXml(value)); + } + } + + public static string writeXml(object value) + { + using (MemoryStream ms = new MemoryStream()) + { + XmlWriterSettings xmlWriterSettings = new XmlWriterSettings(); + xmlWriterSettings.Encoding = new System.Text.UTF8Encoding(false); + xmlWriterSettings.ConformanceLevel = ConformanceLevel.Document; + xmlWriterSettings.Indent = true; + + using (XmlWriter xmlWriter = XmlWriter.Create(ms, xmlWriterSettings)) + { + xmlWriter.WriteStartDocument(); + //xmlWriter.WriteComment("DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " + "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\""); + xmlWriter.WriteDocType("plist", "-//Apple Computer//DTD PLIST 1.0//EN", "http://www.apple.com/DTDs/PropertyList-1.0.dtd", null); + xmlWriter.WriteStartElement("plist"); + xmlWriter.WriteAttributeString("version", "1.0"); + compose(value, xmlWriter); + xmlWriter.WriteEndElement(); + xmlWriter.WriteEndDocument(); + xmlWriter.Flush(); + xmlWriter.Close(); + return System.Text.Encoding.UTF8.GetString(ms.ToArray()); + } + } + } + + public static void writeBinary(object value, string path) + { + using (BinaryWriter writer = new BinaryWriter(new FileStream(path, FileMode.Create))) + { + writer.Write(writeBinary(value)); + } + } + + public static void writeBinary(object value, Stream stream) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + writer.Write(writeBinary(value)); + } + } + + public static byte[] writeBinary(object value) + { + offsetTable.Clear(); + objectTable.Clear(); + refCount = 0; + objRefSize = 0; + offsetByteSize = 0; + offsetTableOffset = 0; + + //Do not count the root node, subtract by 1 + int totalRefs = countObject(value) - 1; + + refCount = totalRefs; + + objRefSize = RegulateNullBytes(BitConverter.GetBytes(refCount)).Length; + + composeBinary(value); + + writeBinaryString("bplist00", false); + + offsetTableOffset = (long)objectTable.Count; + + offsetTable.Add(objectTable.Count - 8); + + offsetByteSize = RegulateNullBytes(BitConverter.GetBytes(offsetTable[offsetTable.Count - 1])).Length; + + List offsetBytes = new List(); + + offsetTable.Reverse(); + + for (int i = 0; i < offsetTable.Count; i++) + { + offsetTable[i] = objectTable.Count - offsetTable[i]; + byte[] buffer = RegulateNullBytes(BitConverter.GetBytes(offsetTable[i]), offsetByteSize); + Array.Reverse(buffer); + offsetBytes.AddRange(buffer); + } + + objectTable.AddRange(offsetBytes); + + objectTable.AddRange(new byte[6]); + objectTable.Add(Convert.ToByte(offsetByteSize)); + objectTable.Add(Convert.ToByte(objRefSize)); + + var a = BitConverter.GetBytes((long)totalRefs + 1); + Array.Reverse(a); + objectTable.AddRange(a); + + objectTable.AddRange(BitConverter.GetBytes((long)0)); + a = BitConverter.GetBytes(offsetTableOffset); + Array.Reverse(a); + objectTable.AddRange(a); + + return objectTable.ToArray(); + } + + #endregion + + #region Private Functions + + private static object readXml(XmlDocument xml) + { + XmlNode rootNode = xml.DocumentElement.ChildNodes[0]; + return parse(rootNode); + } + + private static object readBinary(byte[] data) + { + offsetTable.Clear(); + List offsetTableBytes = new List(); + objectTable.Clear(); + refCount = 0; + objRefSize = 0; + offsetByteSize = 0; + offsetTableOffset = 0; + + List bList = new List(data); + + List trailer = bList.GetRange(bList.Count - 32, 32); + + parseTrailer(trailer); + + objectTable = bList.GetRange(0, (int)offsetTableOffset); + + offsetTableBytes = bList.GetRange((int)offsetTableOffset, bList.Count - (int)offsetTableOffset - 32); + + parseOffsetTable(offsetTableBytes); + + return parseBinary(0); + } + + private static Dictionary parseDictionary(XmlNode node) + { + XmlNodeList children = node.ChildNodes; + if (children.Count % 2 != 0) + { + throw new DataMisalignedException("Dictionary elements must have an even number of child nodes"); + } + + Dictionary dict = new Dictionary(); + + for (int i = 0; i < children.Count; i += 2) + { + XmlNode keynode = children[i]; + XmlNode valnode = children[i + 1]; + + if (keynode.Name != "key") + { + throw new ApplicationException("expected a key node"); + } + + object result = parse(valnode); + + if (result != null) + { + dict.Add(keynode.InnerText, result); + } + } + + return dict; + } + + private static List parseArray(XmlNode node) + { + List array = new List(); + + foreach (XmlNode child in node.ChildNodes) + { + object result = parse(child); + if (result != null) + { + array.Add(result); + } + } + + return array; + } + + private static void composeArray(List value, XmlWriter writer) + { + writer.WriteStartElement("array"); + foreach (object obj in value) + { + compose(obj, writer); + } + writer.WriteEndElement(); + } + + private static object parse(XmlNode node) + { + switch (node.Name) + { + case "dict": + return parseDictionary(node); + case "array": + return parseArray(node); + case "string": + return node.InnerText; + case "integer": + // int result; + //int.TryParse(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo, out result); + return Convert.ToInt32(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo); + case "real": + return Convert.ToDouble(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo); + case "false": + return false; + case "true": + return true; + case "null": + return null; + case "date": + return XmlConvert.ToDateTime(node.InnerText, XmlDateTimeSerializationMode.Utc); + case "data": + return Convert.FromBase64String(node.InnerText); + } + + throw new ApplicationException(String.Format("Plist Node `{0}' is not supported", node.Name)); + } + + private static void compose(object value, XmlWriter writer) + { + + if (value == null || value is string) + { + writer.WriteElementString("string", value as string); + } + else if (value is int || value is long) + { + writer.WriteElementString("integer", ((int)value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo)); + } + else if (value is System.Collections.Generic.Dictionary || + value.GetType().ToString().StartsWith("System.Collections.Generic.Dictionary`2[System.String")) + { + //Convert to Dictionary + Dictionary dic = value as Dictionary; + if (dic == null) + { + dic = new Dictionary(); + IDictionary idic = (IDictionary)value; + foreach (var key in idic.Keys) + { + dic.Add(key.ToString(), idic[key]); + } + } + writeDictionaryValues(dic, writer); + } + else if (value is List) + { + composeArray((List)value, writer); + } + else if (value is byte[]) + { + writer.WriteElementString("data", Convert.ToBase64String((Byte[])value)); + } + else if (value is float || value is double) + { + writer.WriteElementString("real", ((double)value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo)); + } + else if (value is DateTime) + { + DateTime time = (DateTime)value; + string theString = XmlConvert.ToString(time, XmlDateTimeSerializationMode.Utc); + writer.WriteElementString("date", theString);//, "yyyy-MM-ddTHH:mm:ssZ")); + } + else if (value is bool) + { + writer.WriteElementString(value.ToString().ToLower(), ""); + } + else + { + throw new Exception(String.Format("Value type '{0}' is unhandled", value.GetType().ToString())); + } + } + + private static void writeDictionaryValues(Dictionary dictionary, XmlWriter writer) + { + writer.WriteStartElement("dict"); + foreach (string key in dictionary.Keys) + { + object value = dictionary[key]; + writer.WriteElementString("key", key); + compose(value, writer); + } + writer.WriteEndElement(); + } + + private static int countObject(object value) + { + int count = 0; + switch (value.GetType().ToString()) + { + case "System.Collections.Generic.Dictionary`2[System.String,System.Object]": + Dictionary dict = (Dictionary)value; + foreach (string key in dict.Keys) + { + count += countObject(dict[key]); + } + count += dict.Keys.Count; + count++; + break; + case "System.Collections.Generic.List`1[System.Object]": + List list = (List)value; + foreach (object obj in list) + { + count += countObject(obj); + } + count++; + break; + default: + count++; + break; + } + return count; + } + + private static byte[] writeBinaryDictionary(Dictionary dictionary) + { + List buffer = new List(); + List header = new List(); + List refs = new List(); + for (int i = dictionary.Count - 1; i >= 0; i--) + { + var o = new object[dictionary.Count]; + dictionary.Values.CopyTo(o, 0); + composeBinary(o[i]); + offsetTable.Add(objectTable.Count); + refs.Add(refCount); + refCount--; + } + for (int i = dictionary.Count - 1; i >= 0; i--) + { + var o = new string[dictionary.Count]; + dictionary.Keys.CopyTo(o, 0); + composeBinary(o[i]);//); + offsetTable.Add(objectTable.Count); + refs.Add(refCount); + refCount--; + } + + if (dictionary.Count < 15) + { + header.Add(Convert.ToByte(0xD0 | Convert.ToByte(dictionary.Count))); + } + else + { + header.Add(0xD0 | 0xf); + header.AddRange(writeBinaryInteger(dictionary.Count, false)); + } + + + foreach (int val in refs) + { + byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize); + Array.Reverse(refBuffer); + buffer.InsertRange(0, refBuffer); + } + + buffer.InsertRange(0, header); + + + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] composeBinaryArray(List objects) + { + List buffer = new List(); + List header = new List(); + List refs = new List(); + + for (int i = objects.Count - 1; i >= 0; i--) + { + composeBinary(objects[i]); + offsetTable.Add(objectTable.Count); + refs.Add(refCount); + refCount--; + } + + if (objects.Count < 15) + { + header.Add(Convert.ToByte(0xA0 | Convert.ToByte(objects.Count))); + } + else + { + header.Add(0xA0 | 0xf); + header.AddRange(writeBinaryInteger(objects.Count, false)); + } + + foreach (int val in refs) + { + byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize); + Array.Reverse(refBuffer); + buffer.InsertRange(0, refBuffer); + } + + buffer.InsertRange(0, header); + + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] composeBinary(object obj) + { + byte[] value; + switch (obj.GetType().ToString()) + { + case "System.Collections.Generic.Dictionary`2[System.String,System.Object]": + value = writeBinaryDictionary((Dictionary)obj); + return value; + + case "System.Collections.Generic.List`1[System.Object]": + value = composeBinaryArray((List)obj); + return value; + + case "System.Byte[]": + value = writeBinaryByteArray((byte[])obj); + return value; + + case "System.Double": + value = writeBinaryDouble((double)obj); + return value; + + case "System.Int32": + value = writeBinaryInteger((int)obj, true); + return value; + + case "System.String": + value = writeBinaryString((string)obj, true); + return value; + + case "System.DateTime": + value = writeBinaryDate((DateTime)obj); + return value; + + case "System.Boolean": + value = writeBinaryBool((bool)obj); + return value; + + default: + return new byte[0]; + } + } + + public static byte[] writeBinaryDate(DateTime obj) + { + List buffer = new List(RegulateNullBytes(BitConverter.GetBytes(PlistDateConverter.ConvertToAppleTimeStamp(obj)), 8)); + buffer.Reverse(); + buffer.Insert(0, 0x33); + objectTable.InsertRange(0, buffer); + return buffer.ToArray(); + } + + public static byte[] writeBinaryBool(bool obj) + { + List buffer = new List(new byte[1] { (bool)obj ? (byte)9 : (byte)8 }); + objectTable.InsertRange(0, buffer); + return buffer.ToArray(); + } + + private static byte[] writeBinaryInteger(int value, bool write) + { + List buffer = new List(BitConverter.GetBytes((long)value)); + buffer = new List(RegulateNullBytes(buffer.ToArray())); + while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2))) + buffer.Add(0); + int header = 0x10 | (int)(Math.Log(buffer.Count) / Math.Log(2)); + + buffer.Reverse(); + + buffer.Insert(0, Convert.ToByte(header)); + + if (write) + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] writeBinaryDouble(double value) + { + List buffer = new List(RegulateNullBytes(BitConverter.GetBytes(value), 4)); + while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2))) + buffer.Add(0); + int header = 0x20 | (int)(Math.Log(buffer.Count) / Math.Log(2)); + + buffer.Reverse(); + + buffer.Insert(0, Convert.ToByte(header)); + + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] writeBinaryByteArray(byte[] value) + { + List buffer = new List(value); + List header = new List(); + if (value.Length < 15) + { + header.Add(Convert.ToByte(0x40 | Convert.ToByte(value.Length))); + } + else + { + header.Add(0x40 | 0xf); + header.AddRange(writeBinaryInteger(buffer.Count, false)); + } + + buffer.InsertRange(0, header); + + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] writeBinaryString(string value, bool head) + { + List buffer = new List(); + List header = new List(); + foreach (char chr in value.ToCharArray()) + buffer.Add(Convert.ToByte(chr)); + + if (head) + { + if (value.Length < 15) + { + header.Add(Convert.ToByte(0x50 | Convert.ToByte(value.Length))); + } + else + { + header.Add(0x50 | 0xf); + header.AddRange(writeBinaryInteger(buffer.Count, false)); + } + } + + buffer.InsertRange(0, header); + + objectTable.InsertRange(0, buffer); + + return buffer.ToArray(); + } + + private static byte[] RegulateNullBytes(byte[] value) + { + return RegulateNullBytes(value, 1); + } + + private static byte[] RegulateNullBytes(byte[] value, int minBytes) + { + Array.Reverse(value); + List bytes = new List(value); + for (int i = 0; i < bytes.Count; i++) + { + if (bytes[i] == 0 && bytes.Count > minBytes) + { + bytes.Remove(bytes[i]); + i--; + } + else + break; + } + + if (bytes.Count < minBytes) + { + int dist = minBytes - bytes.Count; + for (int i = 0; i < dist; i++) + bytes.Insert(0, 0); + } + + value = bytes.ToArray(); + Array.Reverse(value); + return value; + } + + private static void parseTrailer(List trailer) + { + offsetByteSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(6, 1).ToArray(), 4), 0); + objRefSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(7, 1).ToArray(), 4), 0); + byte[] refCountBytes = trailer.GetRange(12, 4).ToArray(); + Array.Reverse(refCountBytes); + refCount = BitConverter.ToInt32(refCountBytes, 0); + byte[] offsetTableOffsetBytes = trailer.GetRange(24, 8).ToArray(); + Array.Reverse(offsetTableOffsetBytes); + offsetTableOffset = BitConverter.ToInt64(offsetTableOffsetBytes, 0); + } + + private static void parseOffsetTable(List offsetTableBytes) + { + for (int i = 0; i < offsetTableBytes.Count; i += offsetByteSize) + { + byte[] buffer = offsetTableBytes.GetRange(i, offsetByteSize).ToArray(); + Array.Reverse(buffer); + offsetTable.Add(BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0)); + } + } + + private static object parseBinaryDictionary(int objRef) + { + Dictionary buffer = new Dictionary(); + List refs = new List(); + int refCount = 0; + + int refStartPosition; + refCount = getCount(offsetTable[objRef], out refStartPosition); + + + if (refCount < 15) + refStartPosition = offsetTable[objRef] + 1; + else + refStartPosition = offsetTable[objRef] + 2 + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length; + + for (int i = refStartPosition; i < refStartPosition + refCount * 2 * objRefSize; i += objRefSize) + { + byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray(); + Array.Reverse(refBuffer); + refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0)); + } + + for (int i = 0; i < refCount; i++) + { + buffer.Add((string)parseBinary(refs[i]), parseBinary(refs[i + refCount])); + } + + return buffer; + } + + private static object parseBinaryArray(int objRef) + { + List buffer = new List(); + List refs = new List(); + int refCount = 0; + + int refStartPosition; + refCount = getCount(offsetTable[objRef], out refStartPosition); + + + if (refCount < 15) + refStartPosition = offsetTable[objRef] + 1; + else + //The following integer has a header aswell so we increase the refStartPosition by two to account for that. + refStartPosition = offsetTable[objRef] + 2 + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length; + + for (int i = refStartPosition; i < refStartPosition + refCount * objRefSize; i += objRefSize) + { + byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray(); + Array.Reverse(refBuffer); + refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0)); + } + + for (int i = 0; i < refCount; i++) + { + buffer.Add(parseBinary(refs[i])); + } + + return buffer; + } + + private static int getCount(int bytePosition, out int newBytePosition) + { + byte headerByte = objectTable[bytePosition]; + byte headerByteTrail = Convert.ToByte(headerByte & 0xf); + int count; + if (headerByteTrail < 15) + { + count = headerByteTrail; + newBytePosition = bytePosition + 1; + } + else + count = (int)parseBinaryInt(bytePosition + 1, out newBytePosition); + return count; + } + + private static object parseBinary(int objRef) + { + byte header = objectTable[offsetTable[objRef]]; + switch (header & 0xF0) + { + case 0: + { + //If the byte is + //0 return null + //9 return true + //8 return false + return (objectTable[offsetTable[objRef]] == 0) ? (object)null : ((objectTable[offsetTable[objRef]] == 9) ? true : false); + } + case 0x10: + { + return parseBinaryInt(offsetTable[objRef]); + } + case 0x20: + { + return parseBinaryReal(offsetTable[objRef]); + } + case 0x30: + { + return parseBinaryDate(offsetTable[objRef]); + } + case 0x40: + { + return parseBinaryByteArray(offsetTable[objRef]); + } + case 0x50://String ASCII + { + return parseBinaryAsciiString(offsetTable[objRef]); + } + case 0x60://String Unicode + { + return parseBinaryUnicodeString(offsetTable[objRef]); + } + case 0xD0: + { + return parseBinaryDictionary(objRef); + } + case 0xA0: + { + return parseBinaryArray(objRef); + } + } + throw new Exception("This type is not supported"); + } + + public static object parseBinaryDate(int headerPosition) + { + byte[] buffer = objectTable.GetRange(headerPosition + 1, 8).ToArray(); + Array.Reverse(buffer); + double appleTime = BitConverter.ToDouble(buffer, 0); + DateTime result = PlistDateConverter.ConvertFromAppleTimeStamp(appleTime); + return result; + } + + private static object parseBinaryInt(int headerPosition) + { + int output; + return parseBinaryInt(headerPosition, out output); + } + + private static object parseBinaryInt(int headerPosition, out int newHeaderPosition) + { + byte header = objectTable[headerPosition]; + int byteCount = (int)Math.Pow(2, header & 0xf); + byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray(); + Array.Reverse(buffer); + //Add one to account for the header byte + newHeaderPosition = headerPosition + byteCount + 1; + return BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0); + } + + private static object parseBinaryReal(int headerPosition) + { + byte header = objectTable[headerPosition]; + int byteCount = (int)Math.Pow(2, header & 0xf); + byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray(); + Array.Reverse(buffer); + + return BitConverter.ToDouble(RegulateNullBytes(buffer, 8), 0); + } + + private static object parseBinaryAsciiString(int headerPosition) + { + int charStartPosition; + int charCount = getCount(headerPosition, out charStartPosition); + + var buffer = objectTable.GetRange(charStartPosition, charCount); + return buffer.Count > 0 ? Encoding.ASCII.GetString(buffer.ToArray()) : string.Empty; + } + + private static object parseBinaryUnicodeString(int headerPosition) + { + int charStartPosition; + int charCount = getCount(headerPosition, out charStartPosition); + charCount = charCount * 2; + + byte[] buffer = new byte[charCount]; + byte one, two; + + for (int i = 0; i < charCount; i += 2) + { + one = objectTable.GetRange(charStartPosition + i, 1)[0]; + two = objectTable.GetRange(charStartPosition + i + 1, 1)[0]; + + if (BitConverter.IsLittleEndian) + { + buffer[i] = two; + buffer[i + 1] = one; + } + else + { + buffer[i] = one; + buffer[i + 1] = two; + } + } + + return Encoding.Unicode.GetString(buffer); + } + + private static object parseBinaryByteArray(int headerPosition) + { + int byteStartPosition; + int byteCount = getCount(headerPosition, out byteStartPosition); + return objectTable.GetRange(byteStartPosition, byteCount).ToArray(); + } + + #endregion + } + + public enum plistType + { + Auto, Binary, Xml + } + + public static class PlistDateConverter + { + public static long timeDifference = 978307200; + + public static long GetAppleTime(long unixTime) + { + return unixTime - timeDifference; + } + + public static long GetUnixTime(long appleTime) + { + return appleTime + timeDifference; + } + + public static DateTime ConvertFromAppleTimeStamp(double timestamp) + { + DateTime origin = new DateTime(2001, 1, 1, 0, 0, 0, 0); + return origin.AddSeconds(timestamp); + } + + public static double ConvertToAppleTimeStamp(DateTime date) + { + DateTime begin = new DateTime(2001, 1, 1, 0, 0, 0, 0); + TimeSpan diff = date - begin; + return Math.Floor(diff.TotalSeconds); + } + } +} diff --git a/IPASorter/PlistCS.dll b/IPASorter/PlistCS.dll deleted file mode 100644 index 7d8fb5d91a1c5c08eddd7a837923a555d6b28298..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17920 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 T&t~;Ix^J)i!*#(QvgQ99y;K*^ diff --git a/IPASorter/Program.cs b/IPASorter/Program.cs index 94b2371..517f713 100644 --- a/IPASorter/Program.cs +++ b/IPASorter/Program.cs @@ -2,42 +2,63 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; using System.Security.Cryptography; -using System.Text; using System.Text.Json; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace IPASorter { class Program { - // renaming format: "com.bundle.id-1.0-(iOS4.3).ipa" + // renaming format: "com.bundle.id-1.0-(iOS4.3)-md5.ipa" public static List files = new List(); + public static List problematics = new List(); + public static Stopwatch LocalWatch = new Stopwatch(); + public static Stopwatch GlobalWatch = new Stopwatch(); + public static TimeSpan FSElapsedTime; + public static TimeSpan IPElapsedTime; + public static TimeSpan SFElapsedTime; static void Main(string[] args) { Console.WriteLine("IPASorter by KawaiiZenbo"); - if(Directory.Exists("./sortertemp")) - { - Directory.Delete("./sortertemp", true); - } + // start timer + GlobalWatch.Restart(); // parse filepath if given string argsFilePath = args.Length != 0 ? args[0] : "./"; if (!argsFilePath.EndsWith("/")) argsFilePath += "/"; - + if (Directory.Exists($"./{argsFilePath}/sortertemp")) + { + Directory.Delete($"./{argsFilePath}/sortertemp", true); + } + Console.WriteLine($"Using path \"{argsFilePath}\""); // run steps + LocalWatch.Restart(); FileScanner(argsFilePath); - // MD5Eliminator(); obsolete + LocalWatch.Restart(); InfoPlistRenamer(argsFilePath); + LocalWatch.Restart(); SortByiOSCompatibility(argsFilePath); - GenerateJson(); + GlobalWatch.Stop(); + + Console.WriteLine("Generating apps JSON"); + AppList apps = new AppList(); + apps.apps = files.ToArray(); + string appsJson = JsonSerializer.Serialize(apps); + File.WriteAllText($"{argsFilePath}/apps.json", appsJson); Console.WriteLine("complete :)"); + string timeData = $"Total elapsed time (hh:mm:ss): {GlobalWatch.Elapsed}\n" + + $"FileScanner: {FSElapsedTime}\n" + + $"InfoPlistRenamer: {IPElapsedTime}\n" + + $"SortByiOSCompatibility: {SFElapsedTime}\n"; + Console.WriteLine(timeData); + File.WriteAllText($"{argsFilePath}/timeData.txt", timeData); } // step 1 @@ -48,13 +69,23 @@ namespace IPASorter foreach (string s in tmp) { Console.WriteLine($"Found {s}"); - files.Add(new IPAFile + try { - fileName = s.Split('/')[s.Split('/').Length -1], - path = s, - md5sum = CalculateMD5(s) - }) ; + string smd5 = CalculateMD5(s); + if (smd5.StartsWith("ERROR: ")) throw new IOException(); + files.Add(new IPAFile + { + fileName = Path.GetFileName(s), + path = s, + md5sum = smd5 + }); + } + catch + { + Console.WriteLine($"{s} was unable to be added"); + } } + FSElapsedTime = LocalWatch.Elapsed; } // step 2 @@ -64,13 +95,51 @@ namespace IPASorter Directory.CreateDirectory($"{path}/incomplete"); foreach (IPAFile i in files) { - Console.WriteLine($"fixing name of {i.fileName}"); - - // extract ipa - Directory.CreateDirectory($"./sortertemp/{i.fileName}"); try { + Console.WriteLine($"fixing name of {i.fileName}"); + + // extract ipa + Directory.CreateDirectory($"./sortertemp/{i.fileName}"); ZipFile.ExtractToDirectory(i.path, $"./sortertemp/{i.fileName}"); + // parse plist + Dictionary plist = new Dictionary(); + string appPath = $"./sortertemp/{i.fileName}/Payload/{Path.GetFileName(Directory.GetDirectories($"./sortertemp/{i.fileName}/Payload/")[0])}"; + plist = (Dictionary)Plist.readPlist(appPath + "/Info.plist"); + Directory.Delete($"./sortertemp/{i.fileName}", true); + i.CFBundleIdentifier = plist["CFBundleIdentifier"].ToString(); + try + { + i.CFBundleDisplayName = RemoveIllegalFileNameChars(plist["CFBundleDisplayName"].ToString()); + } + catch (KeyNotFoundException) + { + i.CFBundleDisplayName = i.CFBundleIdentifier.Split('.')[2]; + } + if (i.CFBundleDisplayName.Trim() == "") + { + i.CFBundleDisplayName = i.CFBundleIdentifier.Split('.')[2]; + } + string whichToUse = "CFBundleVersion"; + if (plist["CFBundleVersion"].ToString() == "1") + { + whichToUse = "CFBundleShortVersionString"; + } + i.CFBundleVersion = plist[whichToUse].ToString(); + try + { + i.MinimumOSVersion = plist["MinimumOSVersion"].ToString(); + } + catch (KeyNotFoundException) + { + i.MinimumOSVersion = "2.0"; + } + + // rename file + string newFileName = $"{i.CFBundleDisplayName}-({i.CFBundleIdentifier})-{i.CFBundleVersion}-(iOS_{i.MinimumOSVersion})-{i.md5sum}.ipa"; + File.Move(i.path, i.path.Replace(i.fileName, newFileName), true); + i.path = i.path.Replace(i.fileName, newFileName); + i.fileName = newFileName; } catch (Exception) { @@ -78,55 +147,12 @@ namespace IPASorter File.Move(i.path, $"{path}/incomplete/{i.fileName.Replace(".ipa", $"-{i.md5sum}.ipa")}", true); i.path = $"{path}/incomplete/{i.fileName.Replace(".ipa", $"-{i.md5sum}.ipa")}"; i.MinimumOSVersion = "DO NOT ENUMERATE"; + Directory.Delete($"./sortertemp/{i.fileName}", true); continue; } - // parse plist - Dictionary plist = new Dictionary(); - try - { - string appPath = $"./sortertemp/{i.fileName}/Payload/{Directory.GetDirectories($"./sortertemp/{i.fileName}/Payload/")[0].Split('/')[Directory.GetDirectories($"./sortertemp/{i.fileName}/Payload/")[0].Split('/').Length - 1]}"; - plist = (Dictionary)Plist.readPlist(appPath + "/Info.plist"); - } - catch(Exception) - { - Console.WriteLine($"{i.fileName} has a missing/damaged Info.plist. moving to the broken directory..."); - File.Move(i.path, $"{path}/incomplete/{i.fileName.Replace(".ipa", $"-{i.md5sum}.ipa")}", true); - i.path = $"{path}/incomplete/{i.fileName.Replace(".ipa", $"-{i.md5sum}.ipa")}"; - i.MinimumOSVersion = "DO NOT ENUMERATE"; - continue; - } - Directory.Delete($"./sortertemp/{i.fileName}", true); - i.CFBundleIdentifier = plist["CFBundleIdentifier"].ToString(); - try - { - i.CFBundleDisplayName = RemoveIllegalFileNameChars(plist["CFBundleDisplayName"].ToString()); - } - catch (KeyNotFoundException) - { - i.CFBundleDisplayName = i.CFBundleIdentifier.Split('.')[2]; - } - string whichToUse = "CFBundleVersion"; - if (plist["CFBundleVersion"].ToString() == "1") - { - whichToUse = "CFBundleShortVersionString"; - } - i.CFBundleVersion = plist[whichToUse].ToString(); - try - { - i.MinimumOSVersion = plist["MinimumOSVersion"].ToString(); - } - catch (KeyNotFoundException) - { - i.MinimumOSVersion = "2.0"; - } - - // rename file - string newFileName = $"{i.CFBundleDisplayName}-({i.CFBundleIdentifier})-{i.CFBundleVersion}-(iOS_{i.MinimumOSVersion})-{i.md5sum}.ipa"; - File.Move(i.path, i.path.Replace(i.fileName, newFileName), true); - i.path = i.path.Replace(i.fileName, newFileName); - i.fileName = newFileName; } Directory.Delete("./sortertemp", true); + IPElapsedTime = LocalWatch.Elapsed; } // step 3 @@ -136,33 +162,40 @@ namespace IPASorter foreach(IPAFile i in files) { - if (i.MinimumOSVersion == "DO NOT ENUMERATE") continue; - Directory.CreateDirectory($"{path}/iOS{i.MinimumOSVersion.Split('.')[0]}/{i.CFBundleIdentifier}"); - File.Move(i.path, $"{path}/iOS{i.MinimumOSVersion.Split('.')[0]}/{i.CFBundleIdentifier}/{i.fileName}", true); - i.path = $"{path}/iOS{i.MinimumOSVersion.Split('.')[0]}/{i.CFBundleIdentifier}/{i.fileName}"; + try + { + if (i.MinimumOSVersion == "DO NOT ENUMERATE") continue; + string newPath = $"{path}/iOS-{i.MinimumOSVersion.Split('.')[0]}/{i.CFBundleIdentifier}"; + Directory.CreateDirectory(newPath); + File.Move(i.path, newPath + $"/{Path.GetFileName(i.path)}", true); + i.path = newPath + $"/{Path.GetFileName(i.path)}"; + } + catch (Exception e) + { + Console.WriteLine($"Couldnt move {i.path}: {e.Message}"); + } } - } - - // step 4 - static void GenerateJson() - { - AppList apps = new AppList(); - apps.apps = files.ToArray(); - string appsJson = JsonSerializer.Serialize(apps); - File.WriteAllText("./apps.json", appsJson); + SFElapsedTime = LocalWatch.Elapsed; } // other stuff (hate) static string CalculateMD5(string fileName) { - using (var md5 = MD5.Create()) + try { - using (var stream = File.OpenRead(fileName)) + using (var md5 = MD5.Create()) { - var hash = md5.ComputeHash(stream); - return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + using (var stream = File.OpenRead(fileName)) + { + var hash = md5.ComputeHash(stream); + return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + } } } + catch (Exception ex) + { + return $"ERROR: {ex.Message}"; + } } public static string RemoveIllegalFileNameChars(string input, string replacement = "") @@ -172,5 +205,21 @@ namespace IPASorter return r.Replace(input, replacement); } } + + public class IPAFile + { + public string path { get; set; } + public string md5sum { get; set; } + public string fileName { get; set; } + public string CFBundleIdentifier { get; set; } + public string CFBundleVersion { get; set; } + public string MinimumOSVersion { get; set; } + public string CFBundleDisplayName { get; set; } + } + + public class AppList + { + public IPAFile[] apps { get; set; } + } }