diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a516d1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +################################################################################ +# Diese .gitignore-Datei wurde von Microsoft(R) Visual Studio automatisch erstellt. +################################################################################ + +/JavaDecompiler/bin +/JavaDecompiler/obj +/MinecraftTabPatcher/bin +/MinecraftTabPatcher/obj +/.vs +*.user diff --git a/JavaDecompiler/Exceptions/JavaClassMagicNumberException.cs b/JavaDecompiler/Exceptions/JavaClassMagicNumberException.cs new file mode 100644 index 0000000..b95af88 --- /dev/null +++ b/JavaDecompiler/Exceptions/JavaClassMagicNumberException.cs @@ -0,0 +1,11 @@ +using System; + +namespace JavaDecompiler.Exceptions +{ + /// + /// DS 2019-08-09: The exception if the given class file has an invalid magic number. + /// + public class JavaClassMagicNumberException : Exception + { + } +} diff --git a/JavaDecompiler/JavaAccessFlag.cs b/JavaDecompiler/JavaAccessFlag.cs new file mode 100644 index 0000000..4242707 --- /dev/null +++ b/JavaDecompiler/JavaAccessFlag.cs @@ -0,0 +1,20 @@ +using System; + +namespace JavaDecompiler +{ + /// + /// DS 2019-08-09: The access flag for classes and methods + /// + [Flags] + public enum JavaAccessFlag : ushort + { + Public = 0x0001, + Final = 0x0010, + Super = 0x0020, + Interface = 0x0200, + Abstract = 0x0400, + Synthetic = 0x1000, + Annotation = 0x2000, + Enum = 0x4000, + } +} diff --git a/JavaDecompiler/JavaAttributeInfo.cs b/JavaDecompiler/JavaAttributeInfo.cs new file mode 100644 index 0000000..af8f455 --- /dev/null +++ b/JavaDecompiler/JavaAttributeInfo.cs @@ -0,0 +1,270 @@ +using System.IO; + +namespace JavaDecompiler +{ + /// + /// DS 2019-08-09: The attribute info for method, fields and classes + /// + public abstract class JavaAttributeInfo + { + /// + /// Gets and sets the index of the name + /// + public ushort NameIndex { get; set; } + + /// + /// Gets the data length + /// + protected uint Length { get; set; } + + #region Read & write + + /// + /// Recalculates the length. + /// This will be executed when the class file is saved. + /// + /// + public abstract uint CalculateLength(); + + /// + /// Reads the attribute info + /// + /// + /// + public abstract void Read(BinaryReader reader, JavaClass javaClass); + + /// + /// Writes the attribute info data + /// + /// + protected abstract void WriteData(BinaryWriter writer); + + /// + /// Writes the attribute info + /// + /// + public void Write(BinaryWriter writer) + { + // Updates the length + Length = CalculateLength(); + + writer.Write(NameIndex); + writer.Write(Length); + + // Writes the data + WriteData(writer); + } + + /// + /// Creates and read the attribute info + /// + /// + /// + /// + public static JavaAttributeInfo CreateAndRead(BinaryReader reader, JavaClass javaClass) + { + var nameIndex = reader.ReadUInt16(); + var length = reader.ReadUInt32(); + + // Gets the type + var type = javaClass.GetConstantUtf8(nameIndex); + var attribute = Create(type); + attribute.NameIndex = nameIndex; + attribute.Length = length; + attribute.Read(reader, javaClass); + return attribute; + } + + /// + /// Creates a new attribute by the type + /// + /// + /// + private static JavaAttributeInfo Create(string type) + { + switch (type) + { + case "Code": + return new JavaAttributeCodeInfo(); + default: + return new JavaAttributeUnknwonInfo(); + } + } + + #endregion Read & write + } + + #region Child classes + + // TODO: Add all the other classes if needed + + #region Code + + /// + /// DS 2019-08-09: The attribute info for the code + /// + public class JavaAttributeCodeInfo : JavaAttributeInfo + { + /// + /// Gets and sets the max stack size + /// + public ushort MaxStack { get; set; } + + /// + /// Gets and sets the max size for local variables + /// + public ushort MaxLocals { get; set; } + + /// + /// Gets and sets the java byte code + /// + public byte[] Code { get; set; } + + /// + /// Gets and sets the exceptions + /// + public JavaExceptionTable[] Exceptions { get; set; } + + /// + /// Gets and sets the attributes + /// + public JavaAttributeInfo[] Attributes { get; set; } + + #region Read & write + + /// + /// Recalculates the length of the data + /// + /// + public override uint CalculateLength() + { + uint size = sizeof(ushort) * 2; // MaxStack and MaxLocals + size += sizeof(uint) + (uint)Code.Length; // Code + size += sizeof(ushort) + sizeof(ushort) * 4 * (uint)Exceptions.Length; // Exception table + + // Attributes + size += sizeof(ushort); + var attributes = Attributes.Length; + for (int i = 0; i < attributes; i++) + { + size += sizeof(ushort) + sizeof(uint) + Attributes[i].CalculateLength(); + } + + return size; + } + + /// + /// Reads the attribute info + /// + /// + /// + public override void Read(BinaryReader reader, JavaClass javaClass) + { + MaxStack = reader.ReadUInt16(); + MaxLocals = reader.ReadUInt16(); + + // Reads the code + var len = reader.ReadUInt32(); + Code = reader.ReadBytes((int)len); + + // Reads the exception + var exceptions = reader.ReadUInt16(); + Exceptions = new JavaExceptionTable[exceptions]; + for (int i = 0; i < exceptions; i++) + { + Exceptions[i] = JavaExceptionTable.CreateAndRead(reader, javaClass); + } + + // Reads the attributes + var attributes = reader.ReadUInt16(); + Attributes = new JavaAttributeInfo[attributes]; + for (int i = 0; i < attributes; i++) + { + Attributes[i] = JavaAttributeInfo.CreateAndRead(reader, javaClass); + } + } + + /// + /// Writes the attribute info data + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(MaxStack); + writer.Write(MaxLocals); + + // Writes the code + writer.Write((uint)Code.Length); + writer.Write(Code); + + // Writes the exceptions + var exceptions = (ushort)Exceptions.Length; + writer.Write(exceptions); + for (int i = 0; i < exceptions; i++) + { + Exceptions[i].Write(writer); + } + + // Writes the attributes + var attributes = (ushort)Attributes.Length; + writer.Write(attributes); + for (int i = 0; i < attributes; i++) + { + Attributes[i].Write(writer); + } + } + + #endregion Read & write + } + + #endregion Code + + #region Unknwon + + /// + /// DS 2019-08-09: The attribute info for unknown types + /// + public class JavaAttributeUnknwonInfo : JavaAttributeInfo + { + /// + /// Gets and sets the unknown data + /// + public byte[] Data { get; set; } + + #region Read & write + + /// + /// Recalculates the length of the data + /// + /// + public override uint CalculateLength() + { + return (uint)Data.Length; + } + + /// + /// Reads the attribute info + /// + /// + /// + public override void Read(BinaryReader reader, JavaClass javaClass) + { + Data = reader.ReadBytes((int)Length); + } + + /// + /// Writes the attribute info data + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(Data); + } + + #endregion Read & write + } + + #endregion Unknown + + #endregion Child classes +} diff --git a/JavaDecompiler/JavaClass.cs b/JavaDecompiler/JavaClass.cs new file mode 100644 index 0000000..6ce4275 --- /dev/null +++ b/JavaDecompiler/JavaClass.cs @@ -0,0 +1,356 @@ +using JavaDecompiler.Exceptions; +using JavaDecompiler.Utils; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace JavaDecompiler +{ + /// + /// DS 2019-08-09: The parser for the java class files. + /// The format is described here: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html + /// + public class JavaClass + { + #region Const + + /// + /// The magic number for all java classes + /// + public const uint MagicNumber = 0xCAFEBABE; + + #endregion Const + + /// + /// The minor version of java + /// + public ushort MinorVersion { get; set; } + + /// + /// The major version of java + /// + public ushort MajorVersion { get; set; } + + /// + /// The constant pool + /// + public JavaConstantInfo[] ConstantPool { get; set; } + + /// + /// The access flag for this class + /// + public JavaAccessFlag AccessFlag { get; set; } + + /// + /// The constant index for the current class. + /// + public ushort ThisClass { get; set; } + + /// + /// The constant index for the super class. + /// Zero means the super class is java.object. + /// + public ushort SuperClass { get; set; } + + /// + /// The constant indices for all interfaces. + /// + public ushort[] Interfaces { get; set; } + + /// + /// The field info for this class + /// + public JavaFieldInfo[] Fields { get; set; } + + /// + /// The method info for this class + /// + public JavaMethodInfo[] Methods { get; set; } + + /// + /// The attributes for this class + /// + public JavaAttributeInfo[] Attributes { get; set; } + + #region Read & write + + /// + /// Reads the class file from the given file path + /// + /// + public void Read(string path) + { + using (var stream = new FileStream(path, FileMode.Open)) + { + Read(stream); + } + } + + /// + /// Reads the class file by the given stream. + /// + /// + public void Read(Stream stream) + { + Read(new BinaryReaderBigEndian(stream)); + } + + /// + /// Reads the class file by the given binary reader. + /// Remember: This must be an big endian reader! + /// + /// + public void Read(BinaryReader reader) + { + // Reads the magic number + var magic = reader.ReadUInt32(); + if (magic != MagicNumber) + { + throw new JavaClassMagicNumberException(); + } + MinorVersion = reader.ReadUInt16(); + MajorVersion = reader.ReadUInt16(); + + // Reads the constant pool. + // The first constnat (0) is always zero. + // The first valid index is 1. + var constants = reader.ReadUInt16(); + ConstantPool = new JavaConstantInfo[constants]; + for (int i = 1; i < constants; i++) + { + var constant = JavaConstantInfo.CreateAndRead(reader); + ConstantPool[i] = constant; + + // There is an odd special case for double and long. + // For some reasons you have to add an empty index + // if you read an double or long constant. + if (constant.Type == ConstantInfoType.Double || + constant.Type == ConstantInfoType.Long) + { + i++; + } + } + + AccessFlag = (JavaAccessFlag)reader.ReadUInt16(); + ThisClass = reader.ReadUInt16(); + SuperClass = reader.ReadUInt16(); + + // Reads the interfaces + var interfaces = reader.ReadUInt16(); + Interfaces = new ushort[interfaces]; + for (int i = 0; i < interfaces; i++) + { + Interfaces[i] = reader.ReadUInt16(); + } + + // Reads the fields + var fields = reader.ReadUInt16(); + Fields = new JavaFieldInfo[fields]; + for (int i = 0; i < fields; i++) + { + Fields[i] = JavaFieldInfo.CreateAndRead(reader, this); + } + + // Reads the methods + var methods = reader.ReadUInt16(); + Methods = new JavaMethodInfo[methods]; + for (int i = 0; i < methods; i++) + { + Methods[i] = JavaMethodInfo.CreateAndRead(reader, this); + } + + // Reads the attributes + var attributes = reader.ReadUInt16(); + Attributes = new JavaAttributeInfo[attributes]; + for (int i = 0; i < attributes; i++) + { + Attributes[i] = JavaAttributeInfo.CreateAndRead(reader, this); + } + } + + /// + /// Writes the class file to the given file path + /// + /// + public void Write(string path) + { + using (var stream = new FileStream(path, FileMode.Create)) + { + Write(stream); + } + } + + /// + /// Writes the class file to the given stream. + /// + /// + public void Write(Stream stream) + { + using (var writer = new BinaryWriterBigEndian(stream, true)) + { + Write(writer); + } + } + + /// + /// Writes the class file to the given binary writer. + /// Remember: This must be an big endian writer! + /// + /// + public void Write(BinaryWriter writer) + { + writer.Write(MagicNumber); + writer.Write(MinorVersion); + writer.Write(MajorVersion); + + // Writes the constant pool + var constants = (ushort)ConstantPool.Length; + writer.Write(constants); + for (int i = 0; i < constants; i++) + { + var constant = ConstantPool[i]; + if (constant != null) + { + constant.Write(writer); + } + } + + writer.Write((ushort)AccessFlag); + writer.Write(ThisClass); + writer.Write(SuperClass); + + // Writes the interfaces + var interfaces = (ushort)Interfaces.Length; + writer.Write(interfaces); + for (int i = 0; i < interfaces; i++) + { + writer.Write(Interfaces[i]); + } + + // Writes the fields + var fields = (ushort)Fields.Length; + writer.Write(fields); + for (int i = 0; i < fields; i++) + { + Fields[i].Write(writer); + } + + // Writes the methods + var methods = (ushort)Methods.Length; + writer.Write(methods); + for (int i = 0; i < methods; i++) + { + Methods[i].Write(writer); + } + + // Writes the attributes + var attributes = (ushort)Attributes.Length; + writer.Write(attributes); + for (int i = 0; i < attributes; i++) + { + Attributes[i].Write(writer); + } + } + + #endregion Read & write + + #region Method + + /// + /// Returns a method with the given name + /// + /// + /// + public JavaMethodInfo GetMethod(string name) + { + var nameIndex = GetConstantUtf8Index(name); + + return Methods.FirstOrDefault(m => m.NameIndex == nameIndex); + } + + /// + /// Returns all methods with the given name + /// + /// + /// + public IEnumerable GetMethods(string name) + { + var nameIndex = GetConstantUtf8Index(name); + + return Methods.Where(m => m.NameIndex == nameIndex); + } + + /// + /// Returns a method with the given name and descriptor + /// + /// + /// + public JavaMethodInfo GetMethod(string name, string descriptor) + { + var nameIndex = GetConstantUtf8Index(name); + var descriptorIndex = GetConstantUtf8Index(descriptor); + + return Methods.FirstOrDefault(m => m.NameIndex == nameIndex && m.DescriptorIndex == descriptorIndex); + } + + + #endregion Method + + #region Constants + + /// + /// Returns the constant with the given index + /// + /// + /// + public JavaConstantInfo GetConstant(int index) + { + return ConstantPool[index]; + } + + /// + /// Returns the constant with the given index and the given type + /// + /// + /// + /// + public T GetConstant(int index) where T : JavaConstantInfo + { + return ConstantPool[index] as T; + } + + /// + /// Returns the utf8 text at the given index + /// + /// + /// + public string GetConstantUtf8(int index) + { + return GetConstant(index).Value; + } + + /// + /// Returns the index from the constant pool of the given utf8 + /// + /// + /// + public ushort GetConstantUtf8Index(string value) + { + var len = ConstantPool.Length; + for (ushort i = 0; i < len; i++) + { + var constant = ConstantPool[i]; + if (constant is JavaConstantUtf8Info utf8) + { + if (utf8.Value == value) + { + return i; + } + } + } + return 0; + } + + #endregion Constants + } +} diff --git a/JavaDecompiler/JavaConstantInfo.cs b/JavaDecompiler/JavaConstantInfo.cs new file mode 100644 index 0000000..e1809fb --- /dev/null +++ b/JavaDecompiler/JavaConstantInfo.cs @@ -0,0 +1,697 @@ +using System; +using System.IO; +using System.Text; + +namespace JavaDecompiler +{ + /// + /// DS 2019-08-09: The type of the constant info + /// + public enum ConstantInfoType : byte + { + Class = 7, + FieldRef = 9, + MethodRef = 10, + InterfaceMethodRef = 11, + String = 8, + Integer = 3, + Float = 4, + Long = 5, + Double = 6, + NameAndType = 12, + Utf8 = 1, + MethodHandle = 15, + MethodType = 16, + InvokeDynamic = 18, + } + + /// + /// DS 2019-08-09: The constant info parent class + /// + public abstract class JavaConstantInfo + { + /// + /// Gets the constant type + /// + public abstract ConstantInfoType Type { get; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public abstract void Read(BinaryReader reader); + + /// + /// Writes the constant info data + /// + /// + protected abstract void WriteData(BinaryWriter writer); + + /// + /// Writes the constant info + /// + /// + public void Write(BinaryWriter writer) + { + writer.Write((byte)Type); + WriteData(writer); + } + + /// + /// Creates and reads a java constant info + /// + /// + /// + public static JavaConstantInfo CreateAndRead(BinaryReader reader) + { + // Reads the type + var type = (ConstantInfoType)reader.ReadByte(); + var constant = Create(type); + constant.Read(reader); + return constant; + } + + /// + /// Creates a java constant info by its type + /// + /// + /// + private static JavaConstantInfo Create(ConstantInfoType type) + { + switch (type) + { + case ConstantInfoType.Class: + return new JavaConstantClassInfo(); + case ConstantInfoType.String: + return new JavaConstantStringInfo(); + case ConstantInfoType.NameAndType: + return new JavaConstantNameAndTypeInfo(); + case ConstantInfoType.Utf8: + return new JavaConstantUtf8Info(); + case ConstantInfoType.FieldRef: + return new JavaConstantFieldRefInfo(); + case ConstantInfoType.MethodRef: + return new JavaConstantMethodRefInfo(); + case ConstantInfoType.InterfaceMethodRef: + return new JavaConstantInterfaceMethodRefInfo(); + case ConstantInfoType.MethodHandle: + return new JavaConstantMethodHandleInfo(); + case ConstantInfoType.MethodType: + return new JavaConstantMethodTypeInfo(); + case ConstantInfoType.InvokeDynamic: + return new JavaConstantInvokeDynamicInfo(); + case ConstantInfoType.Integer: + return new JavaConstantIntegerInfo(); + case ConstantInfoType.Long: + return new JavaConstantLongInfo(); + case ConstantInfoType.Float: + return new JavaConstantFloatInfo(); + case ConstantInfoType.Double: + return new JavaConstantDoubleInfo(); + default: + throw new ArgumentException(); + } + } + + #endregion Read & write + } + + #region Child classes + + #region Class + + /// + /// DS 2019-08-09: The constant info for classes + /// + public class JavaConstantClassInfo : JavaConstantInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.Class; + + /// + /// Gets and sets the index of the name + /// + public ushort NameIndex { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + NameIndex = reader.ReadUInt16(); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(NameIndex); + } + + #endregion Read & write + } + + #endregion Class + + #region String + + /// + /// DS 2019-08-09: The constant info for strings + /// + public class JavaConstantStringInfo : JavaConstantInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.String; + + /// + /// Gets and sets the index of the string + /// + public ushort StringIndex { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + StringIndex = reader.ReadUInt16(); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(StringIndex); + } + + #endregion Read & write + } + + #endregion String + + #region Name and type + + /// + /// DS 2019-08-09: The constant info for name and types + /// + public class JavaConstantNameAndTypeInfo : JavaConstantInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.NameAndType; + + /// + /// Gets and sets the index of the name + /// + public ushort NameIndex { get; set; } + + /// + /// Gets and sets the index of the descriptor + /// + public ushort DescriptorIndex { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + NameIndex = reader.ReadUInt16(); + DescriptorIndex = reader.ReadUInt16(); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(NameIndex); + writer.Write(DescriptorIndex); + } + + #endregion Read & write + } + + #endregion Name and type + + #region Utf8 + + /// + /// DS 2019-08-09: The constant info for utf8 texts + /// + public class JavaConstantUtf8Info : JavaConstantInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.Utf8; + + /// + /// Gets the text value. + /// TODO: Write a setter. Java uses a wierd big endian version of utf8 when dealing with mulibytes. + /// I will ignore this for now. We can only read single byte chars. Any other char will be gibberish + /// and can not be converted back! + /// You are not able to modify the string directly but you can modify the bytes. + /// + public string Value { get; private set; } + + /// + /// Gets and sets the bytes of the utf8 string + /// + public byte[] Bytes { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + var len = reader.ReadUInt16(); + Bytes = reader.ReadBytes(len); + Value = Encoding.UTF8.GetString(Bytes); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + // TODO: Write a custom encoding for java utf8 and convert the string back to bytes. + writer.Write((ushort)Bytes.Length); + writer.Write(Bytes); + } + + #endregion Read & write + } + + #endregion Utf8 + + #region Ref + + /// + /// DS 2019-08-09: The constant info for references + /// + public abstract class JavaConstantRefInfo : JavaConstantInfo + { + /// + /// Gets and sets the index of the class + /// + public ushort ClassIndex { get; set; } + + /// + /// Gets and sets the index of the name and type + /// + public ushort NameAndTypeIndex { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + ClassIndex = reader.ReadUInt16(); + NameAndTypeIndex = reader.ReadUInt16(); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(ClassIndex); + writer.Write(NameAndTypeIndex); + } + + #endregion Read & write + } + + /// + /// DS 2019-08-09: The constant info for field references + /// + public class JavaConstantFieldRefInfo : JavaConstantRefInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.FieldRef; + } + + /// + /// DS 2019-08-09: The constant info for method references + /// + public class JavaConstantMethodRefInfo : JavaConstantRefInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.MethodRef; + } + + /// + /// DS 2019-08-09: The constant info for interface method references + /// + public class JavaConstantInterfaceMethodRefInfo : JavaConstantRefInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.InterfaceMethodRef; + } + + #endregion Ref + + #region Method handler + + /// + /// DS 2019-08-09: The constant info for method handles + /// + public class JavaConstantMethodHandleInfo : JavaConstantInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.MethodHandle; + + /// + /// Gets and sets the type of the reference + /// + public JavaMethodHandlerType ReferenceType { get; set; } + + /// + /// Gets and sets the index of the reference + /// + public ushort ReferenceIndex { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + ReferenceType = (JavaMethodHandlerType)reader.ReadByte(); + ReferenceIndex = reader.ReadUInt16(); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write((byte)ReferenceType); + writer.Write(ReferenceIndex); + } + + #endregion Read & write + } + + #endregion Method handler + + #region Method type + + /// + /// DS 2019-08-09: The constant info for method types + /// + public class JavaConstantMethodTypeInfo : JavaConstantInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.MethodType; + + /// + /// Gets and sets the index of the descriptor + /// + public ushort DescriptorIndex { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + DescriptorIndex = reader.ReadUInt16(); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(DescriptorIndex); + } + + #endregion Read & write + } + + #endregion Method type + + #region Invoke dynamic + + /// + /// DS 2019-08-09: The constant info for invoke dynamics + /// + public class JavaConstantInvokeDynamicInfo : JavaConstantInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.InvokeDynamic; + + /// + /// Gets and sets the index of the method attribute index + /// + public ushort BootstrapMethodAttrIndex { get; set; } + + /// + /// Gets and sets the index for the name and type + /// + public ushort NameAndTypeIndex { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + BootstrapMethodAttrIndex = reader.ReadUInt16(); + NameAndTypeIndex = reader.ReadUInt16(); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(BootstrapMethodAttrIndex); + writer.Write(NameAndTypeIndex); + } + + #endregion Read & write + } + + #endregion Invoke dynamic + + #region Integer + + /// + /// DS 2019-08-09: The constant info for integers + /// + public class JavaConstantIntegerInfo : JavaConstantInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.Integer; + + /// + /// Gets and sets the value + /// + public int Value { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + Value = reader.ReadInt32(); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(Value); + } + + #endregion Read & write + } + + #endregion Integer + + #region Long + + /// + /// DS 2019-08-09: The constant info for longs + /// + public class JavaConstantLongInfo : JavaConstantInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.Long; + + /// + /// Gets and sets the value + /// + public long Value { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + Value = reader.ReadInt64(); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(Value); + } + + #endregion Read & write + } + + #endregion Long + + #region Float + + /// + /// DS 2019-08-09: The constant info for floats + /// + public class JavaConstantFloatInfo : JavaConstantInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.Float; + + /// + /// Gets and sets the value + /// + public float Value { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + Value = reader.ReadSingle(); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(Value); + } + + #endregion Read & write + } + + #endregion Float + + #region Double + + /// + /// DS 2019-08-09: The constant info for doubles + /// + public class JavaConstantDoubleInfo : JavaConstantInfo + { + /// + /// Gets the constant info type + /// + public override ConstantInfoType Type => ConstantInfoType.Double; + + /// + /// Gets and sets the value + /// + public double Value { get; set; } + + #region Read & write + + /// + /// Reads the constant info + /// + /// + public override void Read(BinaryReader reader) + { + Value = reader.ReadDouble(); + } + + /// + /// Writes the constant info + /// + /// + protected override void WriteData(BinaryWriter writer) + { + writer.Write(Value); + } + + #endregion Read & write + } + + #endregion Double + + #endregion Chils classes +} diff --git a/JavaDecompiler/JavaDecompiler.csproj b/JavaDecompiler/JavaDecompiler.csproj new file mode 100644 index 0000000..5c44a62 --- /dev/null +++ b/JavaDecompiler/JavaDecompiler.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp2.1 + + + + + + + diff --git a/JavaDecompiler/JavaExceptionTable.cs b/JavaDecompiler/JavaExceptionTable.cs new file mode 100644 index 0000000..2e232bf --- /dev/null +++ b/JavaDecompiler/JavaExceptionTable.cs @@ -0,0 +1,72 @@ +using System.IO; + +namespace JavaDecompiler +{ + /// + /// DS 2019-08-09: The java exception table for the code attributes + /// + public class JavaExceptionTable + { + /// + /// Gets and sets the start position of the exception handler + /// + public ushort StartPC { get; set; } + + /// + /// Gets and sets the end position of the exception handler + /// + public ushort EndPC { get; set; } + + /// + /// Gets and sets the handler + /// + public ushort HandlerPC { get; set; } + + /// + /// Gets and sets the catch type + /// + public ushort CatchType { get; set; } + + #region Read & write + + /// + /// Reads the exception table + /// + /// + /// + public void Read(BinaryReader reader, JavaClass javaClass) + { + StartPC = reader.ReadUInt16(); + EndPC = reader.ReadUInt16(); + HandlerPC = reader.ReadUInt16(); + CatchType = reader.ReadUInt16(); + } + + /// + /// Writes the exception table + /// + /// + public void Write(BinaryWriter writer) + { + writer.Write(StartPC); + writer.Write(EndPC); + writer.Write(HandlerPC); + writer.Write(CatchType); + } + + /// + /// Creates and reads the exception table + /// + /// + /// + /// + public static JavaExceptionTable CreateAndRead(BinaryReader reader, JavaClass javaClass) + { + var exceptionTable = new JavaExceptionTable(); + exceptionTable.Read(reader, javaClass); + return exceptionTable; + } + + #endregion Read & write + } +} diff --git a/JavaDecompiler/JavaFieldInfo.cs b/JavaDecompiler/JavaFieldInfo.cs new file mode 100644 index 0000000..40a3118 --- /dev/null +++ b/JavaDecompiler/JavaFieldInfo.cs @@ -0,0 +1,87 @@ +using System.IO; + +namespace JavaDecompiler +{ + /// + /// DS 2019-08-09: The field info of a java class + /// + public class JavaFieldInfo + { + /// + /// Gets and sets the access flag + /// + public JavaAccessFlag AccessFlag { get; set; } + + /// + /// Gets and sets the index of the name + /// + public ushort NameIndex { get; set; } + + /// + /// Gets and sets the index of the descriptor + /// + public ushort DescriptorIndex { get; set; } + + /// + /// Gets and sets the attributes + /// + public JavaAttributeInfo[] Attributes { get; set; } + + #region Read & write + + /// + /// Reads the field info + /// + /// + /// + public void Read(BinaryReader reader, JavaClass javaClass) + { + AccessFlag = (JavaAccessFlag)reader.ReadUInt16(); + NameIndex = reader.ReadUInt16(); + DescriptorIndex = reader.ReadUInt16(); + + // Reads the attributes + var attributes = reader.ReadUInt16(); + Attributes = new JavaAttributeInfo[attributes]; + for (int i = 0; i < attributes; i++) + { + Attributes[i] = JavaAttributeInfo.CreateAndRead(reader, javaClass); + } + + } + + /// + /// Writes the field info + /// + /// + public void Write(BinaryWriter writer) + { + writer.Write((ushort)AccessFlag); + writer.Write(NameIndex); + writer.Write(DescriptorIndex); + + // Writes the attributes + var attributes = (ushort)Attributes.Length; + writer.Write(attributes); + for (int i = 0; i < attributes; i++) + { + Attributes[i].Write(writer); + } + } + + /// + /// Creates and reads the field info + /// + /// + /// + /// + public static JavaFieldInfo CreateAndRead(BinaryReader reader, JavaClass javaClass) + { + var field = new JavaFieldInfo(); + field.Read(reader, javaClass); + return field; + } + + #endregion Read & write + } +} diff --git a/JavaDecompiler/JavaFile.cs b/JavaDecompiler/JavaFile.cs new file mode 100644 index 0000000..19580a7 --- /dev/null +++ b/JavaDecompiler/JavaFile.cs @@ -0,0 +1,136 @@ +using ICSharpCode.SharpZipLib.Zip; +using System; +using System.Collections.Generic; +using System.IO; + +namespace JavaDecompiler +{ + /// + /// DS 2019-08-09: The primary java file (jar). This wile contains all ressources and class files. + /// A jar file is basically just a fancy zip file. + /// + public class JavaFile : IDisposable + { + /// + /// Creates the zip file from the given stream + /// + /// + public JavaFile(Stream stream) + { + m_Zip = new ZipFile(stream); + } + + /// + /// Creates the zip file from the given stream + /// + /// + public JavaFile(string path) : this(new FileStream(path, FileMode.Open, FileAccess.Read)) + { + } + + #region Zip + + /// + /// The zip file + /// + private ZipFile m_Zip; + + /// + /// Gets the internal zip reader + /// + public ZipFile Zip + { + get { return m_Zip; } + } + + /// + /// Returns the reader file stream for a file inside the jar file + /// + /// + /// + public Stream GetFileStream(string path) + { + var entry = m_Zip.GetEntry(path); + return GetFileStream(entry); + } + + /// + /// Returns the reader file stream for a file inside the jar file + /// + /// + /// + public Stream GetFileStream(ZipEntry entry) + { + return m_Zip.GetInputStream(entry); + } + + /// + /// Returns a list of all class files + /// + /// + public IEnumerable GetClassFiles() + { + var len = m_Zip.Count; + for (int i = 0; i < len; i++) + { + var entry = m_Zip[i]; + if (Path.GetExtension(entry.Name) == ".class") + { + yield return entry.Name; + } + } + } + + /// + /// Returns a class file by its name + /// + /// + /// + public JavaClass GetClass(string path) + { + using (var stream = GetFileStream(path)) + { + var javaClass = new JavaClass(); + javaClass.Read(stream); + return javaClass; + } + } + + /// + /// Returns a class file from the zip entry + /// + /// + /// + public JavaClass GetClass(ZipEntry entry) + { + using (var stream = GetFileStream(entry)) + { + var javaClass = new JavaClass(); + javaClass.Read(stream); + return javaClass; + } + } + + /// + /// Close the file + /// + public void Close() + { + m_Zip.Close(); + } + + #endregion Zip + + #region IDisposable + + /// + /// Dispose the file + /// + public void Dispose() + { + Close(); + } + + #endregion IDisposable + } +} diff --git a/JavaDecompiler/JavaMethodHandlerType.cs b/JavaDecompiler/JavaMethodHandlerType.cs new file mode 100644 index 0000000..3c63790 --- /dev/null +++ b/JavaDecompiler/JavaMethodHandlerType.cs @@ -0,0 +1,18 @@ +namespace JavaDecompiler +{ + /// + /// DS 2019-08-09: The type of the method hander refernece + /// + public enum JavaMethodHandlerType : byte + { + GetField = 1, + GetStatic = 2, + PutField = 3, + PutStatic = 4, + InvokeVirtual = 5, + InvokeStatic = 6, + InvokeSpecial = 7, + NewInvokeSpecial = 8, + InvokeInterface = 9, + } +} diff --git a/JavaDecompiler/JavaMethodInfo.cs b/JavaDecompiler/JavaMethodInfo.cs new file mode 100644 index 0000000..7e5eeaf --- /dev/null +++ b/JavaDecompiler/JavaMethodInfo.cs @@ -0,0 +1,100 @@ +using System.IO; +using System.Linq; + +namespace JavaDecompiler +{ + /// + /// DS 2019-08-09: The method info of a java class + /// + public class JavaMethodInfo + { + /// + /// Gets and sets the access flag + /// + public JavaAccessFlag AccessFlag { get; set; } + + /// + /// Gets and sets the index of the name + /// + public ushort NameIndex { get; set; } + + /// + /// Gets and sets the index of the descriptor + /// + public ushort DescriptorIndex { get; set; } + + /// + /// Gets and sets the attributes + /// + public JavaAttributeInfo[] Attributes { get; set; } + + #region Read & write + + /// + /// Reads the method info + /// + /// + /// + public void Read(BinaryReader reader, JavaClass javaClass) + { + AccessFlag = (JavaAccessFlag)reader.ReadUInt16(); + NameIndex = reader.ReadUInt16(); + DescriptorIndex = reader.ReadUInt16(); + + // Reads the attributes + var attributes = reader.ReadUInt16(); + Attributes = new JavaAttributeInfo[attributes]; + for (int i = 0; i < attributes; i++) + { + Attributes[i] = JavaAttributeInfo.CreateAndRead(reader, javaClass); + } + } + + /// + /// Writes the method info + /// + /// + public void Write(BinaryWriter writer) + { + writer.Write((ushort)AccessFlag); + writer.Write(NameIndex); + writer.Write(DescriptorIndex); + + // Writes the attributes + var attributes = (ushort)Attributes.Length; + writer.Write(attributes); + for (int i = 0; i < attributes; i++) + { + Attributes[i].Write(writer); + } + } + + /// + /// Creates and reads the method info + /// + /// + /// + /// + public static JavaMethodInfo CreateAndRead(BinaryReader reader, JavaClass javaClass) + { + var method = new JavaMethodInfo(); + method.Read(reader, javaClass); + return method; + } + + #endregion Read & write + + #region Utils + + /// + /// Returns the code attribute of this method + /// + /// + public JavaAttributeCodeInfo GetCodeAttribute() + { + return (JavaAttributeCodeInfo)Attributes.FirstOrDefault(a => a is JavaAttributeCodeInfo); + } + + #endregion Utils + } +} diff --git a/JavaDecompiler/Utils/BinaryReaderBigEndian.cs b/JavaDecompiler/Utils/BinaryReaderBigEndian.cs new file mode 100644 index 0000000..e4df687 --- /dev/null +++ b/JavaDecompiler/Utils/BinaryReaderBigEndian.cs @@ -0,0 +1,127 @@ +using System; +using System.IO; +using System.Text; + +namespace JavaDecompiler.Utils +{ + /// + /// DS 2019-08-09: A binary reader that reads files in big endian. + /// Not all method are supported yet! + /// + public class BinaryReaderBigEndian : BinaryReader + { + /// + /// Creates the binary reader from stream + /// + /// + public BinaryReaderBigEndian(Stream input) : base(input, Encoding.UTF8) + { + } + + /// + /// Creates the binary reader from stream + /// + /// + /// + public BinaryReaderBigEndian(Stream input, bool leaveOpen) : base(input, Encoding.UTF8, leaveOpen) + { + } + + /// + /// Reads the next float + /// + /// + public override float ReadSingle() + { + var data = base.ReadBytes(4); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToSingle(data, 0); + } + + /// + /// Reads the next double + /// + /// + public override double ReadDouble() + { + var data = base.ReadBytes(8); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToDouble(data, 0); + } + + /// + /// Reasd the next short + /// + /// + public override short ReadInt16() + { + var data = base.ReadBytes(2); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToInt16(data, 0); + } + + /// + /// Reads the next integer + /// + /// + public override int ReadInt32() + { + var data = base.ReadBytes(4); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToInt32(data, 0); + } + + /// + /// Reads the next long + /// + /// + public override long ReadInt64() + { + var data = base.ReadBytes(8); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToInt64(data, 0); + } + + /// + /// Reads the next unsigned short + /// + /// + public override ushort ReadUInt16() + { + var data = base.ReadBytes(2); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToUInt16(data, 0); + } + + /// + /// Reads the next unsigned integer + /// + /// + public override uint ReadUInt32() + { + var data = base.ReadBytes(4); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToUInt32(data, 0); + } + + /// + /// Reads the next unsigned long + /// + /// + public override ulong ReadUInt64() + { + var data = base.ReadBytes(8); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToUInt64(data, 0); + } + + } +} diff --git a/JavaDecompiler/Utils/BinaryWriterBigEndian.cs b/JavaDecompiler/Utils/BinaryWriterBigEndian.cs new file mode 100644 index 0000000..88355a1 --- /dev/null +++ b/JavaDecompiler/Utils/BinaryWriterBigEndian.cs @@ -0,0 +1,126 @@ +using System; +using System.IO; +using System.Text; + +namespace JavaDecompiler.Utils +{ + /// + /// DS 2019-08-09: A binary writer that writes files in big endian. + /// Not all method are supported yet! + /// + public class BinaryWriterBigEndian : BinaryWriter + { + /// + /// Creates the binary writer from stream + /// + /// + public BinaryWriterBigEndian(Stream output) : base(output, Encoding.UTF8) + { + } + + /// + /// Creates the binary writer from stream with the given encoding + /// + /// + /// + public BinaryWriterBigEndian(Stream output, bool leaveOpen) : base(output, Encoding.UTF8, leaveOpen) + { + } + + /// + /// Writes the float + /// + /// + public override void Write(float value) + { + var data = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + base.Write(data); + } + + /// + /// Writes the double + /// + /// + public override void Write(double value) + { + var data = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + base.Write(data); + } + + /// + /// Writes the short + /// + /// + public override void Write(short value) + { + var data = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + base.Write(data); + } + + /// + /// Writes the integer + /// + /// + public override void Write(int value) + { + var data = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + base.Write(data); + } + + /// + /// Writes the long + /// + /// + public override void Write(long value) + { + var data = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + base.Write(data); + } + + /// + /// Writes the unsigned short + /// + /// + public override void Write(ushort value) + { + var data = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + base.Write(data); + } + + /// + /// Writes the unsigned integer + /// + /// + public override void Write(uint value) + { + var data = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + base.Write(data); + } + + /// + /// Writes the unsigned long + /// + /// + public override void Write(ulong value) + { + var data = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + base.Write(data); + } + } +} diff --git a/MinecraftTabPatcher.sln b/MinecraftTabPatcher.sln new file mode 100644 index 0000000..40788f1 --- /dev/null +++ b/MinecraftTabPatcher.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29020.237 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinecraftTabPatcher", "MinecraftTabPatcher\MinecraftTabPatcher.csproj", "{FFE5EE18-FD80-4F48-BAC5-C63C11016AD1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaDecompiler", "JavaDecompiler\JavaDecompiler.csproj", "{5D26679D-D5C0-406E-B911-769BAAD73FD1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FFE5EE18-FD80-4F48-BAC5-C63C11016AD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFE5EE18-FD80-4F48-BAC5-C63C11016AD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFE5EE18-FD80-4F48-BAC5-C63C11016AD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFE5EE18-FD80-4F48-BAC5-C63C11016AD1}.Release|Any CPU.Build.0 = Release|Any CPU + {5D26679D-D5C0-406E-B911-769BAAD73FD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D26679D-D5C0-406E-B911-769BAAD73FD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D26679D-D5C0-406E-B911-769BAAD73FD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D26679D-D5C0-406E-B911-769BAAD73FD1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {46FD4B4F-1EA3-44CA-92D7-7BDFFCB445DC} + EndGlobalSection +EndGlobal diff --git a/MinecraftTabPatcher/MinecraftTabPatcher.csproj b/MinecraftTabPatcher/MinecraftTabPatcher.csproj new file mode 100644 index 0000000..36b9bf4 --- /dev/null +++ b/MinecraftTabPatcher/MinecraftTabPatcher.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.1 + + + + + + + + + + + diff --git a/MinecraftTabPatcher/MinecraftVersion.cs b/MinecraftTabPatcher/MinecraftVersion.cs new file mode 100644 index 0000000..0a52f66 --- /dev/null +++ b/MinecraftTabPatcher/MinecraftVersion.cs @@ -0,0 +1,199 @@ +using JavaDecompiler; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; + +namespace MinecraftTabPatcher +{ + /// + /// DS 2019-08-10: The class of the minecraft version. + /// A version is a jar file inside the minecraft directory. + /// Minecraft can have multiple versions each with multiple + /// profiles. + /// The versions are located at: + /// Windows: %appdata%/.minecraft/versions + /// + public class MinecraftVersion + { + /// + /// Gets the id of the version + /// + public string ID { get; private set; } + + /// + /// Gets the path of the version + /// + public string Path { get; private set; } + + /// + /// Gets the path of the jar file + /// + public string PathJar + { + get { return System.IO.Path.Combine(Path, ID + ".jar"); } + } + + /// + /// Gets the path of the json file + /// + public string PathJson + { + get { return System.IO.Path.Combine(Path, ID + ".json"); } + } + + /// + /// Gets if this version is valid. + /// This will check if the jar and json file exists. + /// + public bool IsValid + { + get { return File.Exists(PathJar) && File.Exists(PathJson); } + } + + /// + /// Creates the miencraft version by the given path + /// + /// + public MinecraftVersion(string path) + { + Path = path; + + // The id is the directory name + ID = System.IO.Path.GetFileName(path); + + // Creates the directory + Directory.CreateDirectory(path); + } + + /// + /// Opens the jar file + /// + /// + public JavaFile OpenJavaFile() + { + return new JavaFile(PathJar); + } + + /// + /// Writes the json file + /// + /// + public void WriteJsonFile(MinecraftVersionInfo info) + { + WriteJsonFile(PathJson, info); + } + + #region Static + + /// + /// Writes the json file + /// + /// + /// + public static void WriteJsonFile(string path, MinecraftVersionInfo info) + { + // Opens the json file + using (var stream = new FileStream(path, FileMode.Create)) + { + using (var streamWriter = new StreamWriter(stream)) + { + // Creates the json writer + using (var writer = new JsonTextWriter(streamWriter)) + { + writer.Formatting = Formatting.Indented; + + writer.WriteStartObject(); + writer.WritePropertyName("id"); + writer.WriteValue(info.ID); + + if (!string.IsNullOrEmpty(info.InheritsFrom)) + { + writer.WritePropertyName("inheritsFrom"); + writer.WriteValue(info.InheritsFrom); + } + + if (!string.IsNullOrEmpty(info.Type)) + { + writer.WritePropertyName("type"); + writer.WriteValue(info.Type); + } + + if (!string.IsNullOrEmpty(info.MainClass)) + { + writer.WritePropertyName("mainClass"); + writer.WriteValue(info.MainClass); + } + + if (info.MinimumLauncherVersion != 0) + { + writer.WritePropertyName("minimumLauncherVersion"); + writer.WriteValue(info.MinimumLauncherVersion.ToString()); + } + + // Writes the empty arguments object. + writer.WritePropertyName("arguments"); + writer.WriteStartObject(); + writer.WriteEndObject(); + + // Prevents the launcher from redownloading the jar file + // by creating an empty downloads object. + writer.WritePropertyName("downloads"); + writer.WriteStartObject(); + writer.WriteEndObject(); + + writer.WriteEndObject(); + } + } + } + } + + /// + /// Returns the directory for the current version + /// + /// + public static string GetSystemVersionDirectory() + { + // TODO: Other systems + return Environment.ExpandEnvironmentVariables("%appdata%/.minecraft/versions"); + } + + /// + /// Returns all minecraft version from the system version directory. + /// + /// + public static IEnumerable GetVersions() + { + var path = GetSystemVersionDirectory(); + + // Directory is not valid + if (!Directory.Exists(path)) + yield break; + + foreach(var version in GetVersions(path)) + { + yield return version; + } + } + + /// + /// Returns all minecraft version from the version directory. + /// + /// + /// + public static IEnumerable GetVersions(string path) + { + // Checks every sub directory for a minecraft version + foreach (var directory in Directory.EnumerateDirectories(path)) + { + var version = new MinecraftVersion(directory); + if (version.IsValid) + { + yield return version; + } + } + } + + #endregion Static + } +} diff --git a/MinecraftTabPatcher/MinecraftVersionInfo.cs b/MinecraftTabPatcher/MinecraftVersionInfo.cs new file mode 100644 index 0000000..708e182 --- /dev/null +++ b/MinecraftTabPatcher/MinecraftVersionInfo.cs @@ -0,0 +1,33 @@ +namespace MinecraftTabPatcher +{ + /// + /// DS 2019-08-11: A small struct to store the content of the minecraft version json file. + /// + public struct MinecraftVersionInfo + { + /// + /// Gets and sets the id + /// + public string ID { get; set; } + + /// + /// Gets and sets the version id of the parent version + /// + public string InheritsFrom { get; set; } + + /// + /// Gets and sets the type + /// + public string Type { get; set; } + + /// + /// Gets and sets the main class + /// + public string MainClass { get; set; } + + /// + /// Gets and sets the minimum launcher version + /// + public int MinimumLauncherVersion { get; set; } + } +} diff --git a/MinecraftTabPatcher/Program.cs b/MinecraftTabPatcher/Program.cs new file mode 100644 index 0000000..1ef5970 --- /dev/null +++ b/MinecraftTabPatcher/Program.cs @@ -0,0 +1,266 @@ +using ICSharpCode.SharpZipLib.Core; +using ICSharpCode.SharpZipLib.Zip; +using JavaDecompiler; +using System; +using System.IO; +using System.Linq; + +namespace MinecraftTabPatcher +{ + /// + /// DS 2019-08-09: The main application entry point + /// + class Program + { + /// + /// You start here + /// + /// + public static void Main(string[] args) + { + // Gets the version path + var versionPath = MinecraftVersion.GetSystemVersionDirectory(); + + Console.WriteLine("Minecraft-Tab-Patcher"); + Console.WriteLine("------------------------------------------------------"); + Console.WriteLine("This tool can patch any existing minecraft version and"); + Console.WriteLine("remove the ability to 'tab' to the next ui element. "); + Console.WriteLine("The only purpose of this patch is to allow the user"); + Console.WriteLine("to enter and exit the inventory with the 'tab' key."); + Console.WriteLine("Since minecraft 1.13 'tab' will focus the new recipe"); + Console.WriteLine("book when trying to close the inventory."); + Console.WriteLine("------------------------------------------------------"); + Console.WriteLine("Minecraft version directory:"); + Console.WriteLine(versionPath); + Console.WriteLine("------------------------------------------------------"); + Console.WriteLine("Please continue at your own risk!"); + Console.WriteLine(""); + Console.ReadKey(); + + + + Console.Clear(); + var versions = MinecraftVersion.GetVersions(versionPath).OrderByDescending(v => v.ID).ToArray(); + if (versions.Length == 0) + { + Console.WriteLine("Could not find any installed minecraft versions at:"); + Console.WriteLine(versionPath); + Console.ReadKey(); + return; + } + + int selection = 0; + bool loop = true; + do { + Console.WriteLine("Select the minecraft version you want to patch."); + Console.WriteLine("Use the arrow keys to navigate and press enter to confirm."); + for (int i = 0; i < versions.Length; i++) + { + var version = versions[i]; + if (selection == i) + Console.WriteLine(" -> {0}", version.ID); + else + Console.WriteLine(" {0}", version.ID); + } + + var key = Console.ReadKey(); + switch (key.Key) + { + // Cancel + case ConsoleKey.Escape: + return; + + // Move selection down + case ConsoleKey.DownArrow: + selection = (selection + 1) % versions.Length; + break; + + // Move selection up + case ConsoleKey.UpArrow: + selection = selection > 0 ? selection - 1 : versions.Length - 1; + break; + + // Confirm + case ConsoleKey.Enter: + loop = false; + break; + } + Console.Clear(); + } while (loop); + + + // The selected version + MinecraftVersion selectedVersion = versions[selection]; + + // Patch the version + var patchedVersion = Patch(selectedVersion, "tabFix"); + + Console.WriteLine(); + Console.WriteLine("------------------------------------------------------"); + Console.WriteLine("Open the minecraft launcher and create a new profile."); + Console.WriteLine("Select the new version '{0}' and click 'save'.", patchedVersion.ID); + Console.WriteLine("Now launch the game with your new profile selected!"); + Console.WriteLine("Bye!"); + Console.WriteLine(""); + Console.ReadKey(); + } + + + /// + /// The original code. This code must be replaced. + /// + static readonly byte[] OrginalCode = new byte[] { 0x11, 0x01, 0x02 }; + + /// + /// Patches the given minecraft version and returns the new patched version. + /// The patcher will create a new version directory and create a new jar file. + /// The jar file is based on the original . + /// The patcher will copy any assets from the orignal jar file. + /// The content of the META-INF directory will not be copied. + /// All classes will be analysed and scanned for a keyPressed method. + /// The patcher will replace 0x110102 from the keyPressed methods. + /// 0x11 (sipush) is the java bytecode instruction that loads the next two + /// bytes as short value onto the stack. Where 0x0102 is the short value for + /// 258 which is the minecraft keycode for tab. 0x0102 will be replaced with + /// 0xFFFF which is an invalid keycode. + /// + /// + /// + /// + private static MinecraftVersion Patch(MinecraftVersion version, string postfix) + { + Console.WriteLine("Start pathching '{0}'...", version.ID); + Console.WriteLine("------------------------------------------------------"); + + // Creates a new version + var parentPath = Path.GetDirectoryName(version.Path); + + // Searches for the next free directory. + // Is this really necessary? + var template = version.ID + "-" + postfix; + var id = template; + var path = Path.Combine(parentPath, id); + var counter = 1; + while (Directory.Exists(path)) + { + counter++; + id = template + counter; + path = Path.Combine(parentPath, id); + } + + Console.WriteLine("Creating patched version '{0}'...", id); + + // Creates the patched version + var patchedVersion = new MinecraftVersion(path); + + // The copy buffer + var buffer = new byte[1024 * 8]; + + // Opens the java file + using (var javaFile = version.OpenJavaFile()) + { + // Creates the output file + using (var output = new FileStream(patchedVersion.PathJar, FileMode.Create)) + { + // Creates the output zip stream + using (var outputZip = new ZipOutputStream(output)) + { + // Do not use 64 bit zip + outputZip.UseZip64 = UseZip64.Off; + + var files = javaFile.Zip.Count; + for (int i = 0; i < files; i++) + { + var entry = javaFile.Zip[i]; + + + // Ignore the META-INF folder + if (entry.Name.Contains("META-INF/")) + { + continue; + } + + // Creates the output entry file + var outputEntry = new ZipEntry(entry.Name); + outputEntry.DateTime = entry.DateTime; + outputEntry.Size = entry.Size; + outputZip.PutNextEntry(outputEntry); + + // This is a class file + if (Path.GetExtension(entry.Name) == ".class") + { + // Loads the class + var javaClass = javaFile.GetClass(entry); + + // Gets the class info + var javaClassInfo = javaClass.GetConstant(javaClass.ThisClass); + var javaClassName = javaClass.GetConstantUtf8(javaClassInfo.NameIndex); + + // Gets the method + var javaMethod = javaClass.GetMethod("keyPressed"); + + + if (javaMethod != null) + { + // Gets the method info + var javaMethodName = javaClass.GetConstantUtf8(javaMethod.NameIndex); + var javaMethodDescriptor = javaClass.GetConstantUtf8(javaMethod.DescriptorIndex); + + // Gets the code attribute of the method + var javaCodeAttribute = javaMethod.GetCodeAttribute(); + if (javaCodeAttribute != null) + { + var code = javaCodeAttribute.Code; + var index = 0; + while ((index = Utils.BinaryIndexOf(code, OrginalCode, index)) >= 0) + { + Console.WriteLine("Patching bytecode from '{0}.{1}{2}' at position {3}...", javaClassName, javaMethodName, javaMethodDescriptor, index); + + // Change the code + code[index + 1] = 0xFF; + code[index + 2] = 0xFF; + + + index++; + } + } + } + + // Writes the class + javaClass.Write(outputZip); + } + else + { + // Just copy the file + using (var inputStream = javaFile.GetFileStream(entry)) + { + StreamUtils.Copy(inputStream, outputZip, buffer); + } + } + } + } + } + } + + Console.WriteLine("Creating json file..."); + + // Creates the json file + var patchedInfo = new MinecraftVersionInfo() + { + ID = id, + InheritsFrom = version.ID, + Type = "custom", + MainClass = "net.minecraft.client.main.Main", + MinimumLauncherVersion = 21, + }; + + // Write the version json + patchedVersion.WriteJsonFile(patchedInfo); + + Console.WriteLine("Version got patched!"); + + return patchedVersion; + } + + } +} diff --git a/MinecraftTabPatcher/Utils.cs b/MinecraftTabPatcher/Utils.cs new file mode 100644 index 0000000..74877e4 --- /dev/null +++ b/MinecraftTabPatcher/Utils.cs @@ -0,0 +1,41 @@ +namespace MinecraftTabPatcher +{ + /// + /// DS 2019-08-09: A class for utility methods + /// + public static class Utils + { + /// + /// Returns the index of in . + /// Returns -1 if the search array was not found. + /// + /// + /// + /// + /// + public static int BinaryIndexOf(byte[] array, byte[] search, int start = 0) + { + int found = 0; + int foundMax = search.Length; + int len = array.Length; + for (int i = start; i < len; i++) + { + var c = array[i]; + + if (c == search[found]) + { + found++; + if (found == foundMax) + { + return i - foundMax + 1; + } + } + else + { + found = 0; + } + } + return -1; + } + } +}