Skip to content

Instantly share code, notes, and snippets.

@draqoon
Created August 11, 2022 13:49
Show Gist options
  • Save draqoon/bb61b2e25ba5e4351afc79b48b79b129 to your computer and use it in GitHub Desktop.
Save draqoon/bb61b2e25ba5e4351afc79b48b79b129 to your computer and use it in GitHub Desktop.
Tools for Extracting Terraria 1.4 Tmod.
using System.IO.Compression;
using System.Text;
using System.Security.Cryptography;
using Microsoft.Xna.Framework.Graphics;
class Program {
static void Main( string[] _ ) => new Program().MainProc();
private void MainProc() {
var outRootDirectory = @"C:\Temp";
var tmodFiles = new string[] {
@"C:\Program Files (x86)\Steam\steamapps\workshop\content\1281930\2824688072\2022.6\CalamityMod.tmod",
//@"C:\Program Files (x86)\Steam\steamapps\workshop\content\1281930\2824688266\2022.6\CalamityModMusic.tmod",
//@"C:\Program Files (x86)\Steam\steamapps\workshop\content\1281930\2563309347\2022.7\MagicStorage.tmod",
//@"C:\Program Files (x86)\Steam\steamapps\workshop\content\1281930\2563851005\2022.6\WMITF.tmod",
//@"C:\Program Files (x86)\Steam\steamapps\workshop\content\1281930\2565540604\2022.7\AutoTrash.tmod",
};
foreach( var tmodFile in tmodFiles ) {
this.ExtractTmod( tmodFile, outRootDirectory );
}
}
private void ExtractTmod( string tmodFile, string outRootDirectory ) {
using var stream = File.OpenRead( tmodFile );
using var reader = new BinaryReader( stream );
if( Encoding.ASCII.GetString( reader.ReadBytes( 4 ) ) != "TMOD" ) {
throw new Exception( "Magic Header != \"TMOD\"" );
}
var ModLoaderVersion = new Version( reader.ReadString() );
var hash = reader.ReadBytes( 20 );
var Signature = reader.ReadBytes( 256 );
reader.ReadInt32();
var pos = stream.Position;
if( !SHA1.Create().ComputeHash( stream ).SequenceEqual( hash ) ) {
throw new Exception( "ModLoader Load Error : Hash Mismatch Corrupted" );
}
stream.Position = pos;
var modName = reader.ReadString();
var modVersion = new Version( reader.ReadString() );
var fileCount = reader.ReadInt32();
Console.WriteLine( $"modName: {modName}\nmodVersion: {modVersion}\nfileCount: {fileCount}" );
var outTopDirectory = Path.Combine( outRootDirectory, Path.GetFileNameWithoutExtension( tmodFile ) );
var files = new List<(string Name, int Offset, int Length, int CompressedLength)>();
var offset = 0;
for( int i = 0; i < fileCount; i++ ) {
var name = reader.ReadString();
var length = reader.ReadInt32();
var compressedLength = reader.ReadInt32();
files.Add( (name, offset, length, compressedLength) );
offset += compressedLength;
}
var dataStartPos = (int)stream.Position;
for( var i = 0; i < files.Count; i++ ) {
var f = files[i];
var dataOffset = f.Offset + dataStartPos;
Console.WriteLine( $"[{i,4}] {f.Name}" );
byte[] data;
if( f.Length != f.CompressedLength ) {
//圧縮されている
data = new byte[f.CompressedLength];
stream.Position = dataOffset;
stream.Read( data, 0, data.Length );
using var ms = new MemoryStream( data );
using var ds = new DeflateStream( ms, CompressionMode.Decompress );
using var decompressMs = new MemoryStream();
ds.CopyTo( decompressMs );
decompressMs.Position = 0;
data = decompressMs.ToArray();
}
else {
//圧縮されていない
data = new byte[f.Length];
stream.Position = dataOffset;
stream.Read( data, 0, data.Length );
}
var outFile = Path.Combine( outTopDirectory, f.Name );
var outDirectory = Path.GetDirectoryName( outFile );
Directory.CreateDirectory( outDirectory );
using var outStream = new FileStream( outFile, FileMode.OpenOrCreate, FileAccess.Write );
using var outWriter = new BinaryWriter( outStream );
outWriter.Write( data );
if( outFile.EndsWith( ".rawimg" ) ) {
var imgFile = Path.ChangeExtension( outFile, ".png" );
this.SaveAsPng( data, imgFile );
}
}
}
/// <summary>
/// save .rawimg file to PNG.
/// </summary>
/// <param name="data">.rawimg binary data</param>
/// <param name="pngFile">PNG filename</param>
/// <remarks>require FNA.dll, FNA3D.dll & SDL2.dll.</remarks>
private unsafe void SaveAsPng( byte[] data, string pngFile ) {
using var ms = new MemoryStream( data );
using var reader = new BinaryReader( ms );
var formatVersion = reader.ReadInt32();
if( formatVersion != 1 ) {
throw new Exception( "Unknown RawImg Format Version: " + formatVersion );
}
var width = reader.ReadInt32();
var height = reader.ReadInt32();
var imgData = reader.ReadBytes( width * height * 4 );
fixed( byte* pixels = &imgData[0] ) {
using var outStream = new FileStream( pngFile, FileMode.Create );
FNA3D.WritePNGStream( outStream, width, height, width, height, (IntPtr)((void*)pixels) );
}
imgData = null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment