- workaround for Unity 5 vertex buffer anomaly

- re-wrote code for loading and extracting bundle files
- fixed link problem with instances of skinned geometry
- separated normal and bump map texture slots in FBX
- added transparency factor in FBX materials
This commit is contained in:
Radu 2015-11-22 00:38:02 +02:00
parent 1f2635c877
commit 32855d932e
3 changed files with 196 additions and 140 deletions

View File

@ -8,10 +8,9 @@ using Lz4;
namespace Unity_Studio
{
public class BundleFile : IDisposable
public class BundleFile
{
private EndianStream Stream;
public byte ver1;
public int ver1;
public string ver2;
public string ver3;
public List<MemoryAssetsFile> MemoryAssetsFileList = new List<MemoryAssetsFile>();
@ -52,84 +51,101 @@ namespace Unity_Studio
}
}
Stream = new EndianStream(new MemoryStream(filebuffer), EndianType.BigEndian);
using (var b_Stream = new EndianStream(new MemoryStream(filebuffer), EndianType.BigEndian))
{
readBundle(b_Stream);
}
}
else { Stream = new EndianStream(File.OpenRead(fileName), EndianType.BigEndian); }
long magicHeader = Stream.ReadInt64();
if (magicHeader == -361700864190383366 || magicHeader == 6155973689634940258 || magicHeader == 6155973689634611575)
else
{
int dummy = Stream.ReadInt32();
ver1 = Stream.ReadByte();
ver2 = Stream.ReadStringToNull();
ver3 = Stream.ReadStringToNull();
using (var b_Stream = new EndianStream(File.OpenRead(fileName), EndianType.BigEndian))
{
readBundle(b_Stream);
}
}
}
private void readBundle(EndianStream b_Stream)
{
var header = b_Stream.ReadStringToNull();
if (header == "UnityWeb" || header == "UnityRaw" || header == "\xFA\xFA\xFA\xFA\xFA\xFA\xFA\xFA")
{
ver1 = b_Stream.ReadInt32();
ver2 = b_Stream.ReadStringToNull();
ver3 = b_Stream.ReadStringToNull();
if (ver1 < 6) { int bundleSize = b_Stream.ReadInt32(); }
else
{
long bundleSize = b_Stream.ReadInt64();
return;
}
short dummy2 = b_Stream.ReadInt16();
int offset = b_Stream.ReadInt16();
int dummy3 = b_Stream.ReadInt32();
int lzmaChunks = b_Stream.ReadInt32();
int lzmaSize = 0;
int fileSize = Stream.ReadInt32();
short dummy2 = Stream.ReadInt16();
int offset = Stream.ReadInt16();
int dummy3 = Stream.ReadInt32();
int lzmaChunks = Stream.ReadInt32();
long streamSize = 0;
for (int i = 0; i < lzmaChunks; i++)
{
lzmaSize = Stream.ReadInt32();
fileSize = Stream.ReadInt32();
lzmaSize = b_Stream.ReadInt32();
streamSize = b_Stream.ReadInt32();
}
Stream.Position = offset;
switch (magicHeader)
b_Stream.Position = offset;
switch (header)
{
case -361700864190383366: //.bytes
case 6155973689634940258: //UnityWeb
case "\xFA\xFA\xFA\xFA\xFA\xFA\xFA\xFA": //.bytes
case "UnityWeb":
{
byte[] lzmaBuffer = new byte[lzmaSize];
Stream.Read(lzmaBuffer, 0, lzmaSize);
Stream.Close();
Stream.Dispose();
b_Stream.Read(lzmaBuffer, 0, lzmaSize);
Stream = new EndianStream(SevenZip.Compression.LZMA.SevenZipHelper.StreamDecompress(new MemoryStream(lzmaBuffer)), EndianType.BigEndian);
offset = 0;
using (var lzmaStream = new EndianStream(SevenZip.Compression.LZMA.SevenZipHelper.StreamDecompress(new MemoryStream(lzmaBuffer)), EndianType.BigEndian))
{
getFiles(lzmaStream, 0);
}
break;
}
case 6155973689634611575: //UnityRaw
case "UnityRaw":
{
getFiles(b_Stream, offset);
break;
}
}
int fileCount = Stream.ReadInt32();
for (int i = 0; i < fileCount; i++)
{
MemoryAssetsFile memFile = new MemoryAssetsFile();
memFile.fileName = Stream.ReadStringToNull();
int fileOffset = Stream.ReadInt32();
fileOffset += offset;
fileSize = Stream.ReadInt32();
long nextFile = Stream.Position;
Stream.Position = fileOffset;
byte[] buffer = new byte[fileSize];
Stream.Read(buffer, 0, fileSize);
memFile.memStream = new MemoryStream(buffer);
MemoryAssetsFileList.Add(memFile);
Stream.Position = nextFile;
}
}
Stream.Close();
else if (header == "UnityFS")
{
ver1 = b_Stream.ReadInt32();
ver2 = b_Stream.ReadStringToNull();
ver3 = b_Stream.ReadStringToNull();
long bundleSize = b_Stream.ReadInt64();
}
}
~BundleFile()
private void getFiles(EndianStream f_Stream, int offset)
{
Dispose();
}
int fileCount = f_Stream.ReadInt32();
for (int i = 0; i < fileCount; i++)
{
MemoryAssetsFile memFile = new MemoryAssetsFile();
memFile.fileName = f_Stream.ReadStringToNull();
int fileOffset = f_Stream.ReadInt32();
fileOffset += offset;
int fileSize = f_Stream.ReadInt32();
long nextFile = f_Stream.Position;
f_Stream.Position = fileOffset;
public void Dispose()
{
Stream.Dispose();
GC.SuppressFinalize(this);
byte[] buffer = new byte[fileSize];
f_Stream.Read(buffer, 0, fileSize);
memFile.memStream = new MemoryStream(buffer);
MemoryAssetsFileList.Add(memFile);
f_Stream.Position = nextFile;
}
}
}
}

View File

@ -583,6 +583,8 @@ namespace Unity_Studio
BitArray m_CurrentChannels = new BitArray(new int[1] { a_Stream.ReadInt32() });
m_VertexCount = a_Stream.ReadInt32();
//int singleStreamStride = 0;//used tor unity 5
int streamCount = 0;
#region streams for 3.5.0 - 3.5.7
if (version[0] < 4)
@ -608,9 +610,6 @@ namespace Unity_Studio
#region channels and streams for 4.0.0 and later
else
{
//int singleStreamStride = 0;//used tor unity 5
int streamCount = 0;
m_Channels = new ChannelInfo[a_Stream.ReadInt32()];
for (int c = 0; c < m_Channels.Length; c++)
{
@ -639,28 +638,49 @@ namespace Unity_Studio
m_Streams[s].frequency = a_Stream.ReadUInt16();
}
}
else //create streams
{
m_Streams = new StreamInfo[streamCount];
for (int s = 0; s < streamCount; s++)
{
m_Streams[s] = new StreamInfo();
m_Streams[s].channelMask = new BitArray(new int[1] { 0 });
m_Streams[s].offset = 0;
if (s > 0) { m_Streams[s].offset = m_Streams[s - 1].offset + m_Streams[s - 1].stride * m_VertexCount; }
m_Streams[s].stride = 0;
foreach (var m_Channel in m_Channels)
{
if (m_Channel.stream == s) { m_Streams[s].stride += m_Channel.dimension * (4 / (int)Math.Pow(2, m_Channel.format)); }
}
}
}
}
#endregion
//actual Vertex Buffer
byte[] m_DataSize = new byte[a_Stream.ReadInt32()];
a_Stream.Read(m_DataSize, 0, m_DataSize.Length);
if (version[0] >= 5) //create streams
{
m_Streams = new StreamInfo[streamCount];
for (int s = 0; s < streamCount; s++)
{
m_Streams[s] = new StreamInfo();
m_Streams[s].channelMask = new BitArray(new int[1] { 0 });
m_Streams[s].offset = 0;
m_Streams[s].stride = 0;
foreach (var m_Channel in m_Channels)
{
if (m_Channel.stream == s) { m_Streams[s].stride += m_Channel.dimension * (4 / (int)Math.Pow(2, m_Channel.format)); }
}
if (s > 0)
{
m_Streams[s].offset = m_Streams[s - 1].offset + m_Streams[s - 1].stride * m_VertexCount;
//sometimes there are 8 bytes between streams
//this is NOT an alignment, even if sometimes it may seem so
if (streamCount == 2) { m_Streams[s].offset = m_DataSize.Length - m_Streams[s].stride * m_VertexCount; }
else
{
m_VertexCount = 0;
return;
}
/*var absoluteOffset = a_Stream.Position + 4 + m_Streams[s].offset;
if ((absoluteOffset % m_Streams[s].stride) != 0)
{
m_Streams[s].offset += m_Streams[s].stride - (int)(absoluteOffset % m_Streams[s].stride);
}*/
}
}
}
#endregion
#region compute FvF

View File

@ -18,6 +18,7 @@ using System.Web.Script.Serialization;
Load parent nodes even if they are not selected to provide transformations?
For extracting bundles, first check if file exists then decompress
Double-check channelgroup argument in new FMOD Studio API system.playSound method
Font index error in Dreamfall Chapters
*/
namespace Unity_Studio
@ -33,7 +34,8 @@ namespace Unity_Studio
//private AssetsFile mainDataFile = null;
private string mainPath = "";
private string productName = "";
private string[] fileTypes = new string[7] { "maindata.", "level*.", "*.assets", "*.sharedAssets", "CustomAssetBundle-*", "CAB-*", "BuildPlayer-*" };
Dictionary<string, Dictionary<string, string>> jsonMats;
Dictionary<string, SortedDictionary<int, ClassStrStruct>> AllClassStructures = new Dictionary<string, SortedDictionary<int, ClassStrStruct>>();
@ -133,7 +135,6 @@ namespace Unity_Studio
//TODO find a way to read data directly instead of merging files
MergeSplitAssets(mainPath);
string[] fileTypes = new string[7] { "maindata.", "level*.", "*.assets", "*.sharedAssets", "CustomAssetBundle-*", "CAB-*", "BuildPlayer-*" };
for (int t = 0; t < fileTypes.Length; t++)
{
string[] fileNames = Directory.GetFiles(mainPath, fileTypes[t], SearchOption.AllDirectories);
@ -284,56 +285,60 @@ namespace Unity_Studio
{
StatusStripUpdate("Decompressing " + Path.GetFileName(bundleFileName) + "...");
using (BundleFile b_File = new BundleFile(bundleFileName))
BundleFile b_File = new BundleFile(bundleFileName);
List<AssetsFile> b_assetsfileList = new List<AssetsFile>();
foreach (var memFile in b_File.MemoryAssetsFileList) //filter unity files
{
List<AssetsFile> b_assetsfileList = new List<AssetsFile>();
foreach (var memFile in b_File.MemoryAssetsFileList) //filter unity files
bool validAssetsFile = false;
switch (Path.GetExtension(memFile.fileName))
{
bool validAssetsFile = false;
switch (Path.GetExtension(memFile.fileName))
{
case ".assets":
case ".sharedAssets":
validAssetsFile = true;
break;
case "":
if (memFile.fileName == "mainData" || Regex.IsMatch(memFile.fileName, "level.*?") || Regex.IsMatch(memFile.fileName, "CustomAssetBundle-.*?") || Regex.IsMatch(memFile.fileName, "CAB-.*?") || Regex.IsMatch(memFile.fileName, "BuildPlayer-.*?")) { validAssetsFile = true; }
break;
}
if (validAssetsFile)
{
StatusStripUpdate("Loading " + memFile.fileName);
memFile.fileName = Path.GetDirectoryName(bundleFileName) + "\\" + memFile.fileName; //add path for extract location
AssetsFile assetsFile = new AssetsFile(memFile.fileName, new EndianStream(memFile.memStream, EndianType.BigEndian));
if (assetsFile.fileGen == 6 && Path.GetFileName(bundleFileName) != "mainData") //2.6.x and earlier don't have a string version before the preload table
{
//make use of the bundle file version
assetsFile.m_Version = b_File.ver3;
assetsFile.version = Array.ConvertAll((b_File.ver3.Split(new string[] { ".", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "\n" }, StringSplitOptions.RemoveEmptyEntries)), int.Parse);
assetsFile.buildType = b_File.ver3.Split(new string[] { ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }, StringSplitOptions.RemoveEmptyEntries);
}
b_assetsfileList.Add(assetsFile);
}
else
{
memFile.memStream.Close();
}
case ".assets":
case ".sharedAssets":
validAssetsFile = true;
break;
case "":
validAssetsFile = (memFile.fileName == "mainData" ||
Regex.IsMatch(memFile.fileName, "level.*?") ||
Regex.IsMatch(memFile.fileName, "CustomAssetBundle-.*?") ||
Regex.IsMatch(memFile.fileName, "CAB-.*?") ||
Regex.IsMatch(memFile.fileName, "BuildPlayer-.*?"));
break;
}
assetsfileList.AddRange(b_assetsfileList);//will the streams still be available for reading data?
foreach (var assetsFile in b_assetsfileList)
if (validAssetsFile)
{
foreach (var sharedFile in assetsFile.sharedAssetsList)
StatusStripUpdate("Loading " + memFile.fileName);
//create dummy path to be used for asset extraction
memFile.fileName = Path.GetDirectoryName(bundleFileName) + "\\" + memFile.fileName;
AssetsFile assetsFile = new AssetsFile(memFile.fileName, new EndianStream(memFile.memStream, EndianType.BigEndian));
if (assetsFile.fileGen == 6 && Path.GetFileName(bundleFileName) != "mainData") //2.6.x and earlier don't have a string version before the preload table
{
sharedFile.fileName = Path.GetDirectoryName(bundleFileName) + "\\" + sharedFile.fileName;
var loadedSharedFile = b_assetsfileList.Find(aFile => aFile.filePath == sharedFile.fileName);
if (loadedSharedFile != null) { sharedFile.Index = assetsfileList.IndexOf(loadedSharedFile); }
//make use of the bundle file version
assetsFile.m_Version = b_File.ver3;
assetsFile.version = Array.ConvertAll((b_File.ver3.Split(new string[] { ".", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "\n" }, StringSplitOptions.RemoveEmptyEntries)), int.Parse);
assetsFile.buildType = b_File.ver3.Split(new string[] { ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }, StringSplitOptions.RemoveEmptyEntries);
}
b_assetsfileList.Add(assetsFile);
}
else
{
memFile.memStream.Close();
}
}
assetsfileList.AddRange(b_assetsfileList);//will the streams still be available for reading data?
foreach (var assetsFile in b_assetsfileList)
{
foreach (var sharedFile in assetsFile.sharedAssetsList)
{
sharedFile.fileName = Path.GetDirectoryName(bundleFileName) + "\\" + sharedFile.fileName;
var loadedSharedFile = b_assetsfileList.Find(aFile => aFile.filePath == sharedFile.fileName);
if (loadedSharedFile != null) { sharedFile.Index = assetsfileList.IndexOf(loadedSharedFile); }
}
}
}
@ -395,29 +400,29 @@ namespace Unity_Studio
string extractPath = bundleFileName + "_unpacked\\";
Directory.CreateDirectory(extractPath);
using (BundleFile b_File = new BundleFile(bundleFileName))
BundleFile b_File = new BundleFile(bundleFileName);
foreach (var memFile in b_File.MemoryAssetsFileList)
{
foreach (var memFile in b_File.MemoryAssetsFileList)
string filePath = extractPath + memFile.fileName.Replace('/', '\\');
if (!Directory.Exists(Path.GetDirectoryName(filePath)))
{
string filePath = extractPath + memFile.fileName.Replace('/','\\');
if (!Directory.Exists(Path.GetDirectoryName(filePath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
}
if (File.Exists(filePath))
{
StatusStripUpdate("File " + memFile.fileName + " already exists");
}
else
{
StatusStripUpdate("Extracting " + Path.GetFileName(memFile.fileName));
extractedCount += 1;
}
if (File.Exists(filePath))
{
StatusStripUpdate("File " + memFile.fileName + " already exists");
}
else
{
StatusStripUpdate("Extracting " + Path.GetFileName(memFile.fileName));
extractedCount += 1;
using (FileStream file = new FileStream(filePath, FileMode.Create, System.IO.FileAccess.Write))
{
memFile.memStream.WriteTo(file);
}
using (FileStream file = new FileStream(filePath, FileMode.Create, System.IO.FileAccess.Write))
{
memFile.memStream.WriteTo(file);
memFile.memStream.Close();
}
}
}
@ -1039,6 +1044,7 @@ namespace Unity_Studio
{
if (firstSortColumn != e.Column)
{
//sorting column has been changed
reverseSort = false;
secondSortColumn = firstSortColumn;
}
@ -1074,6 +1080,7 @@ namespace Unity_Studio
});
break;
}
assetListView.EndUpdate();
resizeAssetListColumns();
@ -1859,6 +1866,11 @@ namespace Unity_Studio
mb.AppendFormat("\n\t\t\tP: \"ShininessExponent\", \"Number\", \"\", \"A\",{0}", m_Float.second);
mb.AppendFormat("\n\t\t\tP: \"Shininess\", \"Number\", \"\", \"A\",{0}", m_Float.second);
break;
case "_Transparency":
mb.Append("\n\t\t\tP: \"TransparentColor\", \"Color\", \"\", \"A\",1,1,1");
mb.AppendFormat("\n\t\t\tP: \"TransparencyFactor\", \"Number\", \"\", \"A\",{0}", m_Float.second);
mb.AppendFormat("\n\t\t\tP: \"Opacity\", \"Number\", \"\", \"A\",{0}", (1 - m_Float.second));
break;
default:
mb.AppendFormat("\n;\t\t\tP: \"{0}\", \"Number\", \"\", \"A\",{1}", m_Float.first, m_Float.second);
break;
@ -1914,10 +1926,12 @@ namespace Unity_Studio
cb2.Append("SpecularColor\"");
break;
case "_NormalMap":
case "_BumpMap":
case "gNormalSampler":
cb2.Append("NormalMap\"");
break;
case "_BumpMap":
cb2.Append("Bump\"");
break;
default:
cb2.AppendFormat("{0}\"", m_TexEnv.name);
break;
@ -2143,6 +2157,10 @@ namespace Unity_Studio
AssetPreloadData MeshPD;
if (assetsfileList.TryGetGameObject(m_SkinnedMeshRenderer.m_GameObject, out m_GameObject) && assetsfileList.TryGetPD(m_SkinnedMeshRenderer.m_Mesh, out MeshPD))
{
//generate unique Geometry ID for instanced mesh objects
//I should find a way to preserve instances at least when exportDeformers is not selected
var keepID = MeshPD.uniqueID;
MeshPD.uniqueID = SkinnedMeshPD.uniqueID;
Mesh m_Mesh = new Mesh(MeshPD);
MeshFBX(m_Mesh, MeshPD.uniqueID, ob);
@ -2282,6 +2300,8 @@ namespace Unity_Studio
bool stop = true;
}
}
MeshPD.uniqueID = keepID;
}
}