├── .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 | }
--------------------------------------------------------------------------------