- 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 namespace Unity_Studio
{ {
public class BundleFile : IDisposable public class BundleFile
{ {
private EndianStream Stream; public int ver1;
public byte ver1;
public string ver2; public string ver2;
public string ver3; public string ver3;
public List<MemoryAssetsFile> MemoryAssetsFileList = new List<MemoryAssetsFile>(); 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); } else
long magicHeader = Stream.ReadInt64();
if (magicHeader == -361700864190383366 || magicHeader == 6155973689634940258 || magicHeader == 6155973689634611575)
{ {
int dummy = Stream.ReadInt32(); using (var b_Stream = new EndianStream(File.OpenRead(fileName), EndianType.BigEndian))
ver1 = Stream.ReadByte(); {
ver2 = Stream.ReadStringToNull(); readBundle(b_Stream);
ver3 = Stream.ReadStringToNull(); }
}
}
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 lzmaSize = 0;
int fileSize = Stream.ReadInt32(); long streamSize = 0;
short dummy2 = Stream.ReadInt16();
int offset = Stream.ReadInt16();
int dummy3 = Stream.ReadInt32();
int lzmaChunks = Stream.ReadInt32();
for (int i = 0; i < lzmaChunks; i++) for (int i = 0; i < lzmaChunks; i++)
{ {
lzmaSize = Stream.ReadInt32(); lzmaSize = b_Stream.ReadInt32();
fileSize = Stream.ReadInt32(); streamSize = b_Stream.ReadInt32();
} }
Stream.Position = offset; b_Stream.Position = offset;
switch (magicHeader) switch (header)
{ {
case -361700864190383366: //.bytes case "\xFA\xFA\xFA\xFA\xFA\xFA\xFA\xFA": //.bytes
case 6155973689634940258: //UnityWeb case "UnityWeb":
{ {
byte[] lzmaBuffer = new byte[lzmaSize]; byte[] lzmaBuffer = new byte[lzmaSize];
Stream.Read(lzmaBuffer, 0, lzmaSize); b_Stream.Read(lzmaBuffer, 0, lzmaSize);
Stream.Close();
Stream.Dispose();
Stream = new EndianStream(SevenZip.Compression.LZMA.SevenZipHelper.StreamDecompress(new MemoryStream(lzmaBuffer)), EndianType.BigEndian); using (var lzmaStream = new EndianStream(SevenZip.Compression.LZMA.SevenZipHelper.StreamDecompress(new MemoryStream(lzmaBuffer)), EndianType.BigEndian))
offset = 0; {
getFiles(lzmaStream, 0);
}
break; break;
} }
case 6155973689634611575: //UnityRaw case "UnityRaw":
{ {
getFiles(b_Stream, offset);
break; 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;
}
} }
else if (header == "UnityFS")
Stream.Close(); {
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() byte[] buffer = new byte[fileSize];
{ f_Stream.Read(buffer, 0, fileSize);
Stream.Dispose(); memFile.memStream = new MemoryStream(buffer);
GC.SuppressFinalize(this); 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() }); BitArray m_CurrentChannels = new BitArray(new int[1] { a_Stream.ReadInt32() });
m_VertexCount = 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 #region streams for 3.5.0 - 3.5.7
if (version[0] < 4) if (version[0] < 4)
@ -608,9 +610,6 @@ namespace Unity_Studio
#region channels and streams for 4.0.0 and later #region channels and streams for 4.0.0 and later
else else
{ {
//int singleStreamStride = 0;//used tor unity 5
int streamCount = 0;
m_Channels = new ChannelInfo[a_Stream.ReadInt32()]; m_Channels = new ChannelInfo[a_Stream.ReadInt32()];
for (int c = 0; c < m_Channels.Length; c++) for (int c = 0; c < m_Channels.Length; c++)
{ {
@ -639,28 +638,49 @@ namespace Unity_Studio
m_Streams[s].frequency = a_Stream.ReadUInt16(); 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 #endregion
//actual Vertex Buffer //actual Vertex Buffer
byte[] m_DataSize = new byte[a_Stream.ReadInt32()]; byte[] m_DataSize = new byte[a_Stream.ReadInt32()];
a_Stream.Read(m_DataSize, 0, m_DataSize.Length); 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 #endregion
#region compute FvF #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? Load parent nodes even if they are not selected to provide transformations?
For extracting bundles, first check if file exists then decompress For extracting bundles, first check if file exists then decompress
Double-check channelgroup argument in new FMOD Studio API system.playSound method Double-check channelgroup argument in new FMOD Studio API system.playSound method
Font index error in Dreamfall Chapters
*/ */
namespace Unity_Studio namespace Unity_Studio
@ -33,7 +34,8 @@ namespace Unity_Studio
//private AssetsFile mainDataFile = null; //private AssetsFile mainDataFile = null;
private string mainPath = ""; private string mainPath = "";
private string productName = ""; private string productName = "";
private string[] fileTypes = new string[7] { "maindata.", "level*.", "*.assets", "*.sharedAssets", "CustomAssetBundle-*", "CAB-*", "BuildPlayer-*" };
Dictionary<string, Dictionary<string, string>> jsonMats; Dictionary<string, Dictionary<string, string>> jsonMats;
Dictionary<string, SortedDictionary<int, ClassStrStruct>> AllClassStructures = new Dictionary<string, SortedDictionary<int, ClassStrStruct>>(); 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 //TODO find a way to read data directly instead of merging files
MergeSplitAssets(mainPath); MergeSplitAssets(mainPath);
string[] fileTypes = new string[7] { "maindata.", "level*.", "*.assets", "*.sharedAssets", "CustomAssetBundle-*", "CAB-*", "BuildPlayer-*" };
for (int t = 0; t < fileTypes.Length; t++) for (int t = 0; t < fileTypes.Length; t++)
{ {
string[] fileNames = Directory.GetFiles(mainPath, fileTypes[t], SearchOption.AllDirectories); string[] fileNames = Directory.GetFiles(mainPath, fileTypes[t], SearchOption.AllDirectories);
@ -284,56 +285,60 @@ namespace Unity_Studio
{ {
StatusStripUpdate("Decompressing " + Path.GetFileName(bundleFileName) + "..."); 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>(); bool validAssetsFile = false;
switch (Path.GetExtension(memFile.fileName))
foreach (var memFile in b_File.MemoryAssetsFileList) //filter unity files
{ {
bool validAssetsFile = false; case ".assets":
switch (Path.GetExtension(memFile.fileName)) case ".sharedAssets":
{ validAssetsFile = true;
case ".assets": break;
case ".sharedAssets": case "":
validAssetsFile = true; validAssetsFile = (memFile.fileName == "mainData" ||
break; Regex.IsMatch(memFile.fileName, "level.*?") ||
case "": Regex.IsMatch(memFile.fileName, "CustomAssetBundle-.*?") ||
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; } Regex.IsMatch(memFile.fileName, "CAB-.*?") ||
break; Regex.IsMatch(memFile.fileName, "BuildPlayer-.*?"));
} 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();
}
} }
assetsfileList.AddRange(b_assetsfileList);//will the streams still be available for reading data? if (validAssetsFile)
foreach (var assetsFile in b_assetsfileList)
{ {
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; //make use of the bundle file version
var loadedSharedFile = b_assetsfileList.Find(aFile => aFile.filePath == sharedFile.fileName); assetsFile.m_Version = b_File.ver3;
if (loadedSharedFile != null) { sharedFile.Index = assetsfileList.IndexOf(loadedSharedFile); } 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\\"; string extractPath = bundleFileName + "_unpacked\\";
Directory.CreateDirectory(extractPath); 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('/','\\'); Directory.CreateDirectory(Path.GetDirectoryName(filePath));
if (!Directory.Exists(Path.GetDirectoryName(filePath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
} }
if (File.Exists(filePath)) if (File.Exists(filePath))
{ {
StatusStripUpdate("File " + memFile.fileName + " already exists"); StatusStripUpdate("File " + memFile.fileName + " already exists");
} }
else else
{ {
StatusStripUpdate("Extracting " + Path.GetFileName(memFile.fileName)); StatusStripUpdate("Extracting " + Path.GetFileName(memFile.fileName));
extractedCount += 1; extractedCount += 1;
using (FileStream file = new FileStream(filePath, FileMode.Create, System.IO.FileAccess.Write)) using (FileStream file = new FileStream(filePath, FileMode.Create, System.IO.FileAccess.Write))
{ {
memFile.memStream.WriteTo(file); memFile.memStream.WriteTo(file);
} memFile.memStream.Close();
} }
} }
} }
@ -1039,6 +1044,7 @@ namespace Unity_Studio
{ {
if (firstSortColumn != e.Column) if (firstSortColumn != e.Column)
{ {
//sorting column has been changed
reverseSort = false; reverseSort = false;
secondSortColumn = firstSortColumn; secondSortColumn = firstSortColumn;
} }
@ -1074,6 +1080,7 @@ namespace Unity_Studio
}); });
break; break;
} }
assetListView.EndUpdate(); assetListView.EndUpdate();
resizeAssetListColumns(); 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: \"ShininessExponent\", \"Number\", \"\", \"A\",{0}", m_Float.second);
mb.AppendFormat("\n\t\t\tP: \"Shininess\", \"Number\", \"\", \"A\",{0}", m_Float.second); mb.AppendFormat("\n\t\t\tP: \"Shininess\", \"Number\", \"\", \"A\",{0}", m_Float.second);
break; 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: default:
mb.AppendFormat("\n;\t\t\tP: \"{0}\", \"Number\", \"\", \"A\",{1}", m_Float.first, m_Float.second); mb.AppendFormat("\n;\t\t\tP: \"{0}\", \"Number\", \"\", \"A\",{1}", m_Float.first, m_Float.second);
break; break;
@ -1914,10 +1926,12 @@ namespace Unity_Studio
cb2.Append("SpecularColor\""); cb2.Append("SpecularColor\"");
break; break;
case "_NormalMap": case "_NormalMap":
case "_BumpMap":
case "gNormalSampler": case "gNormalSampler":
cb2.Append("NormalMap\""); cb2.Append("NormalMap\"");
break; break;
case "_BumpMap":
cb2.Append("Bump\"");
break;
default: default:
cb2.AppendFormat("{0}\"", m_TexEnv.name); cb2.AppendFormat("{0}\"", m_TexEnv.name);
break; break;
@ -2143,6 +2157,10 @@ namespace Unity_Studio
AssetPreloadData MeshPD; AssetPreloadData MeshPD;
if (assetsfileList.TryGetGameObject(m_SkinnedMeshRenderer.m_GameObject, out m_GameObject) && assetsfileList.TryGetPD(m_SkinnedMeshRenderer.m_Mesh, out 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); Mesh m_Mesh = new Mesh(MeshPD);
MeshFBX(m_Mesh, MeshPD.uniqueID, ob); MeshFBX(m_Mesh, MeshPD.uniqueID, ob);
@ -2282,6 +2300,8 @@ namespace Unity_Studio
bool stop = true; bool stop = true;
} }
} }
MeshPD.uniqueID = keepID;
} }
} }