diff --git a/IPASorter/FileClass.cs b/IPASorter/FileClass.cs new file mode 100644 index 0000000..ac4228b --- /dev/null +++ b/IPASorter/FileClass.cs @@ -0,0 +1,16 @@ +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; } + } +} diff --git a/IPASorter/IPASorter.csproj b/IPASorter/IPASorter.csproj index c73e0d1..d7bd561 100644 --- a/IPASorter/IPASorter.csproj +++ b/IPASorter/IPASorter.csproj @@ -5,4 +5,10 @@ netcoreapp3.1 + + + ..\..\..\..\Documents\GitHub\PlistCS\PlistCS\PlistCS\bin\Release\PlistCS.dll + + + diff --git a/IPASorter/Plist.cs b/IPASorter/Plist.cs deleted file mode 100644 index f52c364..0000000 --- a/IPASorter/Plist.cs +++ /dev/null @@ -1,961 +0,0 @@ -// -// 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/Program.cs b/IPASorter/Program.cs index 0103965..a4b2e97 100644 --- a/IPASorter/Program.cs +++ b/IPASorter/Program.cs @@ -2,220 +2,106 @@ 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.Json; -using System.Text.RegularExpressions; +using System.Text; +using System.Threading.Tasks; namespace IPASorter { class Program { - // renaming format: "com.bundle.id-1.0-(iOS4.3)-md5.ipa" + // renaming format: "com.bundle.id-1.0-(iOS4.3).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"); - // start timer - GlobalWatch.Restart(); + if(Directory.Exists(".\\sortertemp")) + { + Directory.Delete(".\\sortertemp", true); + } // 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); - LocalWatch.Restart(); - InfoPlistRenamer(argsFilePath); - LocalWatch.Restart(); - SortByiOSCompatibility(argsFilePath); - GlobalWatch.Stop(); + string argsFilePath = args.Length != 0 ? args[0] : ".\\"; + if (!argsFilePath.EndsWith("/") || !argsFilePath.EndsWith("\\")) argsFilePath += "/"; - Console.WriteLine("Generating apps JSON"); - AppList apps = new AppList(); - apps.apps = files.ToArray(); - string appsJson = JsonSerializer.Serialize(apps); - File.WriteAllText($"{argsFilePath}/apps.json", appsJson); + // run steps + FileScanner(argsFilePath); + // MD5Eliminator(); obsolete + InfoPlistRenamer(); + if(args.Length > 1 && args[1] == "-si") + { + SortByiOSCompatibility(); + } 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 static void FileScanner(string path) { - Console.WriteLine("Scanning subdirectories for IPA files"); List tmp = Directory.GetFiles(path, "*.ipa", SearchOption.AllDirectories).ToList(); foreach (string s in tmp) { - Console.WriteLine($"Found {s}"); - try + files.Add(new IPAFile { - 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"); - } + fileName = s.Split('/')[s.Split('/').Length -1].Split('\\')[s.Split('/')[s.Split('/').Length - 1].Split('\\').Length - 1], + path = s, + md5sum = CalculateMD5(s) + }) ; } - FSElapsedTime = LocalWatch.Elapsed; } // step 2 - static void InfoPlistRenamer(string path) + static void InfoPlistRenamer() { - Directory.CreateDirectory("./sortertemp"); - Directory.CreateDirectory($"{path}/incomplete"); + Directory.CreateDirectory(".\\sortertemp"); foreach (IPAFile i in files) { - try - { - Console.WriteLine($"fixing name of {i.fileName}"); + 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]; - } - i.CFBundleVersion = plist["CFBundleVersion"].ToString(); - try - { - i.MinimumOSVersion = plist["MinimumOSVersion"].ToString(); - } - catch (KeyNotFoundException) - { - i.MinimumOSVersion = "2.0"; - } + // extract ipa + Directory.CreateDirectory($".\\sortertemp\\{i.fileName}"); + ZipFile.ExtractToDirectory(i.path, $".\\sortertemp\\{i.fileName}"); + string appPath = $".\\sortertemp\\{i.fileName}\\Payload\\{Directory.GetDirectories($".\\sortertemp\\{i.fileName}\\Payload\\")[0].Split('\\')[Directory.GetDirectories($".\\sortertemp\\{i.fileName}\\Payload\\")[0].Split('\\').Length - 1]}"; - // 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) - { - Console.WriteLine($"{i.fileName} is damaged. 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"; - try { Directory.Delete($"./sortertemp/{i.fileName}", true); } - catch { } - continue; - } + // parse plist + Dictionary plist = (Dictionary)Plist.readPlist(appPath + "\\Info.plist"); + Directory.Delete($".\\sortertemp\\{i.fileName}", true); + i.CFBundleIdentifier = plist["CFBundleIdentifier"].ToString(); + i.CFBundleVersion = plist["CFBundleVersion"].ToString(); + i.MinimumOSVersion = plist["MinimumOSVersion"].ToString(); + + // rename file + string newFileName = $"{plist["CFBundleIdentifier"]}-{plist["CFBundleVersion"]}-(iOS_{plist["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; + Directory.Delete(".\\sortertemp", true); } - // step 3 - static void SortByiOSCompatibility(string path) + // optional step 3 + static void SortByiOSCompatibility() { - Console.WriteLine("Sorting apps by minimum iOS version"); - foreach(IPAFile i in files) - { - 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}"); - } - } - SFElapsedTime = LocalWatch.Elapsed; } - // other stuff (hate) + // othet stuff static string CalculateMD5(string fileName) { - try + using (var md5 = MD5.Create()) { - using (var md5 = MD5.Create()) + using (var stream = File.OpenRead(fileName)) { - using (var stream = File.OpenRead(fileName)) - { - var hash = md5.ComputeHash(stream); - return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); - } + 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 = "") - { - var regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); - var r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch))); - 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; } } } diff --git a/LICENSE b/LICENSE deleted file mode 100644 index ecc0572..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 KawaiiZenbo - -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. diff --git a/README.md b/README.md index cb1c98c..293ba5a 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ Cross platform sorter for iOS app packages (IPAs) ## Command line arguments -`IPASorter ` or -`dotnet IPASorter.dll ` -Running with no arguments will sort from the directory the program is in. +`IPASorter ` +Running with no arguments will sort from the directory the program is in with no sorting options. +### Sorting Options +coming soon requires .net core 3.1 or later