├── .gitattributes ├── .gitignore ├── README.md ├── tModUnpacker.sln └── tModUnpacker ├── App.config ├── Properties └── AssemblyInfo.cs ├── RawImage.cs ├── main.cs ├── tMod.cs ├── tModBase.cs ├── tModUnpacker.csproj ├── tModUnpacker.csproj.user └── tMod_Old.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /tModUnpacker/bin/* 2 | /tModUnpacker/obj/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tModUnpacker 2 | 3 | Jus small crappy Terraria tModLoader mod unpacker. 4 | 5 | Usage: 6 | ``` 7 | tModUnpacker.exe /path/to/tmod/file.tmod 8 | 9 | tModUnpacker.exe /path/to/tmod/file.tmod /path/to/outputfolder 10 | ``` 11 | Or just drag-n-drop .tmod file on to exe.
12 | Or even worse, associate .tmod with this crappy unpacker and just doubleclick on the .tmod file! 13 | 14 | #### Purpose 15 | It just unpacks .tmod files as is. Nothing else.
16 | Even if modder has set flags such as `hideCode` and `hideResources` to true. (tModLoader should load these mods somehow, right?) 17 | 18 | #### Downloads 19 | All versions: https://github.com/IVogel/tModUnpacker/releases 20 | 21 | 1.2: https://github.com/IVogel/tModUnpacker/releases/tag/1.2
22 | 1.3: https://github.com/IVogel/tModUnpacker/releases/tag/1.3 23 | -------------------------------------------------------------------------------- /tModUnpacker.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30114.105 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tModUnpacker", "tModUnpacker\tModUnpacker.csproj", "{A120FB8E-7DF8-4D19-84D3-E373D7E63131}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x86 = Debug|x86 12 | Release|Any CPU = Release|Any CPU 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {A120FB8E-7DF8-4D19-84D3-E373D7E63131}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {A120FB8E-7DF8-4D19-84D3-E373D7E63131}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {A120FB8E-7DF8-4D19-84D3-E373D7E63131}.Debug|x86.ActiveCfg = Debug|Any CPU 19 | {A120FB8E-7DF8-4D19-84D3-E373D7E63131}.Debug|x86.Build.0 = Debug|Any CPU 20 | {A120FB8E-7DF8-4D19-84D3-E373D7E63131}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {A120FB8E-7DF8-4D19-84D3-E373D7E63131}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {A120FB8E-7DF8-4D19-84D3-E373D7E63131}.Release|x86.ActiveCfg = Release|Any CPU 23 | {A120FB8E-7DF8-4D19-84D3-E373D7E63131}.Release|x86.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {11EAEC1E-3BF0-482B-9714-4A04BA5EA40D} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /tModUnpacker/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tModUnpacker/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("tModUnpacker")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("tModUnpacker")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a120fb8e-7df8-4d19-84d3-e373d7e63131")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /tModUnpacker/RawImage.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | using System.Runtime.InteropServices; 4 | using System.IO; 5 | 6 | namespace tModUnpacker 7 | { 8 | static class RawImage 9 | { 10 | public static byte[] ToRaw(Bitmap image) 11 | { 12 | BitmapData bitmapData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 13 | int size = image.Width * image.Height * 4; 14 | byte[] data = new byte[size]; 15 | Marshal.Copy(bitmapData.Scan0, data, 0, size); 16 | for (int index = 0; index < size; index = index + 4) 17 | { 18 | byte r = data[index ]; 19 | byte g = data[index + 1]; 20 | byte b = data[index + 2]; 21 | byte a = data[index + 3]; 22 | 23 | data[index ] = b; 24 | data[index + 1] = g; 25 | data[index + 2] = r; 26 | data[index + 3] = a; 27 | } 28 | image.UnlockBits(bitmapData); 29 | return data; 30 | } 31 | 32 | public static Bitmap RawToPng(byte[] image) 33 | { 34 | Stream stream = new MemoryStream(image); 35 | BinaryReader reader = new BinaryReader(stream); 36 | stream.Seek(4, SeekOrigin.Begin); 37 | int width = reader.ReadInt32(); 38 | int height = reader.ReadInt32(); 39 | int size = width * height * 4; 40 | byte[] data = new byte[size]; 41 | 42 | Bitmap output = new Bitmap(width, height, PixelFormat.Format32bppArgb); 43 | 44 | for (int index = 0; index < size; index = index + 4) 45 | { 46 | data[index + 2] = reader.ReadByte(); 47 | data[index + 1] = reader.ReadByte(); 48 | data[index ] = reader.ReadByte(); 49 | data[index + 3] = reader.ReadByte(); 50 | } 51 | BitmapData bitmapData = output.LockBits(new Rectangle(0, 0, output.Width, output.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); 52 | Marshal.Copy(data, 0, bitmapData.Scan0, size); 53 | output.UnlockBits(bitmapData); 54 | return output; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tModUnpacker/main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace tModUnpacker 6 | { 7 | class main 8 | { 9 | static ItMod get_suitable_unpacker(string path) 10 | { 11 | Version ver; 12 | using (FileStream fileStream = File.OpenRead(path)) 13 | { 14 | BinaryReader binaryReader = new BinaryReader(fileStream); 15 | if (Encoding.ASCII.GetString(binaryReader.ReadBytes(4)) != "TMOD") 16 | throw new IOException("Can't read tMod magic bytes"); 17 | ver = new Version(binaryReader.ReadString()); 18 | } 19 | 20 | if (ver < new Version(0, 11)) 21 | return new tMod_Old(path); 22 | else 23 | return new tMod(path); 24 | } 25 | 26 | static void Main(string[] args) 27 | { 28 | int nArgs = args.Length; 29 | if (nArgs < 1) 30 | { 31 | string help = "Usage:\n" + 32 | $"\t{AppDomain.CurrentDomain.FriendlyName} \"/path/to/tmod/file.tmod\"\n" + 33 | "\tWill extract mod in the same folder where .tmod file located is.\n" + 34 | "\n" + 35 | $"\t{AppDomain.CurrentDomain.FriendlyName} \"/path/to/tmod/file.tmod\" \"/path/to/outputfolder\"\n" + 36 | "\tWill extract in specified folder.\n"; 37 | Console.Write(help); 38 | return; 39 | } 40 | string outputpath; 41 | if (nArgs < 2) 42 | { 43 | outputpath = Path.GetDirectoryName(args[0]); 44 | } 45 | else 46 | { 47 | outputpath = args[1]; 48 | } 49 | ItMod mod = get_suitable_unpacker(args[0]); 50 | mod.ReadMod(); 51 | mod.DumpFiles(outputpath, filename => Console.WriteLine($"\tWriting: {filename}")); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tModUnpacker/tMod.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO.Compression; 5 | using System.Collections; 6 | using System; 7 | 8 | namespace tModUnpacker 9 | { 10 | class tMod : tModBase 11 | { 12 | public tMod(string path) : base(path) {} 13 | 14 | ~tMod() 15 | { 16 | if (this.tempfile != null) 17 | this.tempfile.Close(); 18 | } 19 | 20 | override public byte[] GetFile(string path) 21 | { 22 | path = path.Replace("\\", "/"); 23 | if (HasFile(path)) 24 | { 25 | tModFileInfo file = this.files[path]; 26 | this.tempfile.Seek(file.filestart, SeekOrigin.Begin); 27 | 28 | byte[] data = data = new byte[file.filesize]; 29 | if (file.iscompressed) 30 | { 31 | byte[] compresseddata = new byte[file.compressedlen]; 32 | this.tempfile.Read(compresseddata, 0, (int)file.compressedlen); 33 | Stream stream = new MemoryStream(compresseddata); 34 | DeflateStream inflateStream = new DeflateStream(stream, CompressionMode.Decompress); 35 | inflateStream.Read(data, 0, (int)file.filesize); 36 | 37 | } else { 38 | this.tempfile.Read(data, 0, (int)file.filesize); 39 | } 40 | return data; 41 | } 42 | return null; 43 | } 44 | 45 | override public bool ReadMod() 46 | { 47 | tModInfo info = new tModInfo(); 48 | using (FileStream fileStream = File.OpenRead(this.path)) 49 | { 50 | BinaryReader binaryReader = new BinaryReader(fileStream); 51 | if (Encoding.ASCII.GetString(binaryReader.ReadBytes(4)) != "TMOD") 52 | return false; 53 | 54 | info.modloaderversion = new Version(binaryReader.ReadString()); 55 | info.modhash = binaryReader.ReadBytes(20); 56 | info.modsignature = binaryReader.ReadBytes(256); 57 | fileStream.Seek(4, SeekOrigin.Current); 58 | 59 | fileStream.CopyTo(this.tempfile); 60 | this.tempfile.Seek(0, SeekOrigin.Begin); 61 | BinaryReader tempFileBinaryReader = new BinaryReader(this.tempfile); 62 | info.modname = tempFileBinaryReader.ReadString(); 63 | Console.WriteLine(info.modname); 64 | info.modversion = new Version(tempFileBinaryReader.ReadString()); 65 | info.filecount = tempFileBinaryReader.ReadInt32(); 66 | int WTF = 0; 67 | IDictionary wtfDict = new Dictionary(); 68 | for (int index = 0; index < info.filecount; index++) 69 | { 70 | tModFileInfo file = new tModFileInfo(); 71 | string path = tempFileBinaryReader.ReadString().Replace("\\", "/"); 72 | file.filesize = tempFileBinaryReader.ReadInt32(); 73 | file.filestart = WTF; 74 | file.compressedlen = tempFileBinaryReader.ReadInt32(); 75 | WTF += file.compressedlen; 76 | wtfDict.Add(path, file); 77 | } 78 | int datastart = (int)this.tempfile.Position; 79 | foreach (string fileName in wtfDict.Keys) 80 | { 81 | tModFileInfo file = wtfDict[fileName]; 82 | file.filestart += datastart; 83 | this.files.Add(fileName, file); 84 | } 85 | this.info = info; 86 | return true; 87 | } 88 | } 89 | 90 | override public bool DumpFile(string outputpath, string filename) 91 | { 92 | string path = Path.Combine(outputpath, this.info.modname, filename); 93 | byte[] data = this.GetFile(filename); 94 | 95 | if (data == null) 96 | return false; 97 | string ext = Path.GetExtension(filename).ToLower(); 98 | if (ext != null && ext == ".rawimg") 99 | { 100 | System.Drawing.Bitmap image = RawImage.RawToPng(data); 101 | MemoryStream stream = new MemoryStream(); 102 | image.Save(stream, System.Drawing.Imaging.ImageFormat.Png); 103 | WriteFile(Path.ChangeExtension(path, "png"), stream.ToArray()); 104 | return true; 105 | } 106 | WriteFile(path, data); 107 | return true; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tModUnpacker/tModBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | namespace tModUnpacker 7 | { 8 | struct tModFileInfo 9 | { 10 | public long filestart; 11 | public long filesize; 12 | public int compressedlen; 13 | public bool iscompressed 14 | { 15 | get { return compressedlen != filesize; } 16 | } 17 | 18 | } 19 | 20 | struct tModFile 21 | { 22 | public string path; 23 | public long size; 24 | public byte[] data; 25 | } 26 | 27 | struct tModInfo 28 | { 29 | public Version modloaderversion; 30 | public Version modversion; 31 | public string modname; 32 | public byte[] modhash; 33 | public byte[] modsignature; 34 | public int filecount; 35 | } 36 | 37 | interface ItMod 38 | { 39 | bool HasFile(string path); 40 | byte[] GetFile(string path); 41 | void WriteFile(string path, byte[] data); 42 | bool ReadMod(); 43 | bool DumpFile(string outputpath, string filename); 44 | void DumpFiles(string outputpath); 45 | void DumpFiles(string outputpath, System.Action func); 46 | void DumpFiles(string outputpath, System.Func func); 47 | } 48 | 49 | class tModBase : ItMod, IEnumerable, IEnumerable 50 | { 51 | protected tModInfo info; 52 | 53 | protected IDictionary files = new Dictionary(); 54 | 55 | protected string path; 56 | protected FileStream tempfile; 57 | protected string tempfilepath = Path.GetTempFileName(); 58 | 59 | public tModBase(string path) 60 | { 61 | this.tempfile = new FileStream( 62 | this.tempfilepath, 63 | FileMode.OpenOrCreate, 64 | FileAccess.ReadWrite, 65 | FileShare.Read, 66 | 4096, 67 | FileOptions.DeleteOnClose | FileOptions.RandomAccess 68 | ); 69 | this.path = path; 70 | } 71 | 72 | ~tModBase() 73 | { 74 | if (this.tempfile != null) 75 | this.tempfile.Close(); 76 | } 77 | 78 | virtual public bool HasFile(string path) 79 | { 80 | return this.files.ContainsKey(path.Replace("\\", "/")); 81 | } 82 | 83 | virtual public byte[] GetFile(string path) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | 88 | virtual public void WriteFile(string path, byte[] data) 89 | { 90 | string dirpath = Path.GetDirectoryName(path); 91 | if (!Directory.Exists(dirpath)) 92 | { 93 | Directory.CreateDirectory(dirpath); 94 | } 95 | File.WriteAllBytes(path, data); 96 | } 97 | 98 | virtual public bool ReadMod() 99 | { 100 | throw new NotImplementedException(); 101 | } 102 | 103 | virtual public bool DumpFile(string outputpath, string filename) 104 | { 105 | throw new NotImplementedException(); 106 | } 107 | 108 | virtual public void DumpFiles(string outputpath) 109 | { 110 | foreach (string fileName in this.files.Keys) 111 | { 112 | DumpFile(outputpath, fileName); 113 | } 114 | } 115 | 116 | virtual public void DumpFiles(string outputpath, System.Action func) 117 | { 118 | foreach (string fileName in this.files.Keys) 119 | { 120 | func(fileName); 121 | DumpFile(outputpath, fileName); 122 | } 123 | } 124 | 125 | virtual public void DumpFiles(string outputpath, System.Func func) 126 | { 127 | foreach (string fileName in this.files.Keys) 128 | { 129 | if (!func(fileName)) 130 | continue; 131 | DumpFile(outputpath, fileName); 132 | } 133 | } 134 | 135 | virtual public IEnumerator GetEnumerator() 136 | { 137 | foreach (string path in this.files.Keys) 138 | { 139 | tModFile file = new tModFile(); 140 | file.path = path; 141 | file.data = this.GetFile(path); 142 | file.size = file.data != null ? file.data.Length : 0; 143 | yield return file; 144 | } 145 | } 146 | 147 | IEnumerator IEnumerable.GetEnumerator() 148 | { 149 | return this.GetEnumerator(); 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /tModUnpacker/tModUnpacker.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A120FB8E-7DF8-4D19-84D3-E373D7E63131} 8 | Exe 9 | tModUnpacker 10 | tModUnpacker 11 | v4.6.1 12 | 512 13 | true 14 | true 15 | publish\ 16 | true 17 | Disk 18 | false 19 | Foreground 20 | 7 21 | Days 22 | false 23 | false 24 | true 25 | 0 26 | 1.0.0.%2a 27 | false 28 | false 29 | true 30 | 31 | 32 | 33 | 34 | AnyCPU 35 | true 36 | full 37 | false 38 | bin\Debug\ 39 | DEBUG;TRACE 40 | prompt 41 | 4 42 | 43 | 44 | AnyCPU 45 | pdbonly 46 | true 47 | bin\Release\ 48 | TRACE 49 | prompt 50 | 4 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | Designer 74 | 75 | 76 | 77 | 78 | False 79 | Microsoft .NET Framework 4.6.1 %28x86 and x64%29 80 | true 81 | 82 | 83 | False 84 | .NET Framework 3.5 SP1 85 | false 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /tModUnpacker/tModUnpacker.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | publish\ 5 | 6 | 7 | 8 | 9 | 10 | en-US 11 | false 12 | 13 | -------------------------------------------------------------------------------- /tModUnpacker/tMod_Old.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.IO.Compression; 5 | 6 | namespace tModUnpacker { 7 | class tMod_Old : tModBase 8 | { 9 | public tMod_Old(string path) : base(path) {} 10 | 11 | override public bool ReadMod() 12 | { 13 | using (FileStream fileStream = File.OpenRead(this.path)) 14 | { 15 | BinaryReader binaryReader = new BinaryReader(fileStream); 16 | if (Encoding.ASCII.GetString(binaryReader.ReadBytes(4)) != "TMOD") 17 | return false; 18 | 19 | info.modloaderversion = new Version(binaryReader.ReadString()); 20 | info.modhash = binaryReader.ReadBytes(20); 21 | info.modsignature = binaryReader.ReadBytes(256); 22 | fileStream.Seek(4, SeekOrigin.Current); 23 | 24 | DeflateStream inflateStream = new DeflateStream(fileStream, CompressionMode.Decompress); 25 | inflateStream.CopyTo(this.tempfile); 26 | inflateStream.Close(); 27 | 28 | this.tempfile.Seek(0, SeekOrigin.Begin); 29 | BinaryReader tempFileBinaryReader = new BinaryReader(this.tempfile); 30 | 31 | info.modname = tempFileBinaryReader.ReadString(); 32 | info.modversion = new Version(tempFileBinaryReader.ReadString()); 33 | info.filecount = tempFileBinaryReader.ReadInt32(); 34 | 35 | for (int index = 0; index < info.filecount; index++) 36 | { 37 | tModFileInfo file = new tModFileInfo(); 38 | string path = tempFileBinaryReader.ReadString().Replace("\\", "/"); 39 | file.filesize = tempFileBinaryReader.ReadInt32(); 40 | file.filestart = this.tempfile.Position; 41 | this.tempfile.Seek(file.filesize, SeekOrigin.Current); 42 | this.files.Add(path, file); 43 | } 44 | } 45 | return true; 46 | } 47 | 48 | override public byte[] GetFile(string path) 49 | { 50 | path = path.Replace("\\", "/"); 51 | if (HasFile(path)) 52 | { 53 | tModFileInfo file = this.files[path]; 54 | this.tempfile.Seek(file.filestart, SeekOrigin.Begin); 55 | 56 | byte[] data = data = new byte[file.filesize]; 57 | this.tempfile.Read(data, 0, (int)file.filesize); 58 | return data; 59 | } 60 | return null; 61 | } 62 | 63 | override public bool DumpFile(string outputpath, string filename) 64 | { 65 | string path = Path.Combine(outputpath, this.info.modname, filename); 66 | byte[] data = this.GetFile(filename); 67 | 68 | if (data == null) 69 | return false; 70 | string ext = Path.GetExtension(filename).ToLower(); 71 | if (ext != null && ext == ".rawimg") 72 | { 73 | System.Drawing.Bitmap image = RawImage.RawToPng(data); 74 | MemoryStream stream = new MemoryStream(); 75 | image.Save(stream, System.Drawing.Imaging.ImageFormat.Png); 76 | WriteFile(Path.ChangeExtension(path, "png"), stream.ToArray()); 77 | return true; 78 | } 79 | WriteFile(path, data); 80 | return true; 81 | } 82 | } 83 | } --------------------------------------------------------------------------------