public new static Asset Load(string assetPath) { byte[] buff = File.ReadAllBytes(assetPath); var res = JsonSerializer.Deserialize <AssetSprite>(buff, ProjectFile.JsonOptions); using (SHA1Managed sha1 = new SHA1Managed()) { res.Length = buff.Length; string dir = Path.GetDirectoryName(assetPath); int ind = 0; string basePath = Path.Combine(dir, res.Name); string pngPath = basePath + "_" + ind.ToString() + ".png"; while (File.Exists(pngPath)) { // Load PNG file, make new texture item for it Bitmap imgBitmap; using (var temp = new Bitmap(pngPath)) imgBitmap = temp.FullCopy(); res.TextureItems.Add(new GMTextureItem(imgBitmap)); byte[] imgBuff = imgBitmap.GetReadOnlyByteArray(); sha1.TransformBlock(imgBuff, 0, imgBuff.Length, null, 0); res.Length += imgBuff.Length; pngPath = basePath + "_" + (++ind).ToString() + ".png"; } // Load special info buffer if (res.SpecialInfo?.Buffer != null) { string path = Path.Combine(dir, res.SpecialInfo.Buffer); if (File.Exists(path)) { byte[] specialBuff = File.ReadAllBytes(path); res.SpecialInfo.InternalBuffer = new BufferRegion(specialBuff); sha1.TransformBlock(specialBuff, 0, specialBuff.Length, null, 0); res.Length += specialBuff.Length; } } // Load raw collision masks if ((int)res.CollisionMask.Mode < 0) { res.CollisionMask.RawMasks = new List <BufferRegion>(); ind = 0; pngPath = basePath + "_mask_" + ind.ToString() + ".png"; while (File.Exists(pngPath)) { Bitmap imgBitmap; using (var temp = new Bitmap(pngPath)) imgBitmap = temp.FullCopy(); byte[] mask = CollisionMasks.GetMaskFromImage(imgBitmap); res.CollisionMask.RawMasks.Add(new BufferRegion(mask)); sha1.TransformBlock(mask, 0, mask.Length, null, 0); res.Length += mask.Length; pngPath = basePath + "_mask_" + (++ind).ToString() + ".png"; } } sha1.TransformFinalBlock(buff, 0, buff.Length); res.Hash = sha1.Hash; } return(res); }
public override void ConvertData(ProjectFile pf, int index) { GMSprite asset = (GMSprite)pf.Sprites[index].DataAsset; AssetSprite projectAsset = new AssetSprite() { Name = asset.Name?.Content, Transparent = asset.Transparent, Smooth = asset.Smooth, Preload = asset.Preload, Width = asset.Width, Height = asset.Height, OriginX = asset.OriginX, OriginY = asset.OriginY, TextureItems = asset.TextureItems.ToList() }; projectAsset.TextureItems.RemoveAll(i => i == null); // Determine texture group if (projectAsset.TextureItems.Count == 0) { projectAsset.TextureGroup = null; } else { var group = pf.Textures.TextureGroups[ pf.Textures.PageToGroup[projectAsset.TextureItems[0].TexturePageID]]; // If this group only has this sprite, and also has a page for // each item, then this is a separate group if (new HashSet <GMTextureItem>(group.Items).SetEquals(projectAsset.TextureItems) && group.Pages.Count == projectAsset.TextureItems.Count) { projectAsset.SeparateTextureGroup = true; } projectAsset.TextureGroup = group.Name; } // Determine collision mask info List <Bitmap> bitmaps; projectAsset.CollisionMask = CollisionMasks.GetInfoForSprite(pf, asset, out bitmaps); List <BufferRegion> regenerated = CollisionMasks.GetMasksForSprite(pf, projectAsset, out _, bitmaps); if (!CollisionMasks.CompareMasks(asset.CollisionMasks, regenerated)) { bool manual = true; if (projectAsset.CollisionMask.Type == AssetSprite.CollisionMaskInfo.MaskType.Diamond || projectAsset.CollisionMask.Type == AssetSprite.CollisionMaskInfo.MaskType.Ellipse) { // This may be a false positive diamond/ellipse, try suggesting Precise projectAsset.CollisionMask = CollisionMasks.GetInfoForSprite(pf, asset, out bitmaps, true); regenerated = CollisionMasks.GetMasksForSprite(pf, projectAsset, out _, bitmaps); manual = !CollisionMasks.CompareMasks(asset.CollisionMasks, regenerated); } if (manual) { // Need to generate manually projectAsset.CollisionMask.Mode = (AssetSprite.CollisionMaskInfo.MaskMode)(-1 - (int)projectAsset.CollisionMask.Mode); projectAsset.CollisionMask.AlphaTolerance = null; projectAsset.CollisionMask.Left = asset.MarginLeft; projectAsset.CollisionMask.Top = asset.MarginTop; projectAsset.CollisionMask.Right = asset.MarginRight; projectAsset.CollisionMask.Bottom = asset.MarginBottom; projectAsset.CollisionMask.RawMasks = asset.CollisionMasks; } } // Fix incorrect sizes resulting from mods: // - If a texture entry's source width/height are greater than bound width/height, scale the bound width/height // - Check for largest bound width/height, max them with sprite width/height, store in sprite width/height and all bound width/heights bool resized = false; int largestWidth = projectAsset.Width, largestHeight = projectAsset.Height; foreach (var item in projectAsset.TextureItems) { if (item == null) { continue; } if (item.SourceWidth > item.BoundWidth) { item.BoundWidth = item.SourceWidth; } if (item.SourceHeight > item.BoundHeight) { item.BoundHeight = item.SourceHeight; } largestWidth = Math.Max(largestWidth, item.BoundWidth); largestHeight = Math.Max(largestHeight, item.BoundHeight); } if (largestWidth > projectAsset.Width) { resized = true; projectAsset.Width = largestWidth; } if (largestHeight > projectAsset.Height) { resized = true; projectAsset.Height = largestHeight; } if (resized) { foreach (var item in projectAsset.TextureItems) { if (item == null) { continue; } item.BoundWidth = (ushort)projectAsset.Width; item.BoundHeight = (ushort)projectAsset.Height; } if ((int)projectAsset.CollisionMask.Mode < 0) { // Can't use the raw data; it's a different size projectAsset.CollisionMask.Mode = (AssetSprite.CollisionMaskInfo.MaskMode)(-(1 + (int)projectAsset.CollisionMask.Mode)); projectAsset.CollisionMask.RawMasks = null; } } // Now just handle special data if (asset.SpecialOrGMS2) { projectAsset.SpecialInfo = new AssetSprite.SpriteSpecialInfo() { SpriteType = asset.S_SpriteType }; if (asset.S_SpriteType != GMSprite.SpriteType.Normal) { projectAsset.SpecialInfo.Buffer = "buffer.bin"; projectAsset.SpecialInfo.InternalBuffer = asset.S_Buffer; } if (pf.DataHandle.VersionInfo.IsNumberAtLeast(2)) { projectAsset.SpecialInfo.GMS2PlaybackSpeed = asset.GMS2_PlaybackSpeed; projectAsset.SpecialInfo.GMS2PlaybackSpeedType = asset.GMS2_PlaybackSpeedType; if (asset.GMS2_3_Sequence != null) { var seq = asset.GMS2_3_Sequence.Sequence; List <AssetSprite.SpriteSpecialInfo.SequenceInfo.Frame> frames = new(); foreach (var keyframe in (seq.Tracks[0].Keyframes as GMSequence.Track.ValueKeyframes).List) { frames.Add(new() { Position = keyframe.Key, Length = keyframe.Length, Index = keyframe.Channels.Values.First().Value }); } List <AssetSprite.SpriteSpecialInfo.SequenceInfo.BroadcastMessage> broadcastMessages = new(); foreach (var message in seq.BroadcastMessages) { broadcastMessages.Add(new() { Position = message.Key, Message = message.Channels.Values.First().List.First().Content }); } projectAsset.SpecialInfo.Sequence = new AssetSprite.SpriteSpecialInfo.SequenceInfo() { Name = seq.Name?.Content, Frames = frames, BroadcastMessages = broadcastMessages }; } if (asset.GMS2_3_2_NineSlice != null) { projectAsset.SpecialInfo.NineSlice = new AssetSprite.SpriteSpecialInfo.NineSliceInfo() { Left = asset.GMS2_3_2_NineSlice.Left, Top = asset.GMS2_3_2_NineSlice.Top, Right = asset.GMS2_3_2_NineSlice.Right, Bottom = asset.GMS2_3_2_NineSlice.Bottom, Enabled = asset.GMS2_3_2_NineSlice.Enabled, TileModes = asset.GMS2_3_2_NineSlice.TileModes.ToList() }; } } } pf.Sprites[index].Asset = projectAsset; }
protected override byte[] WriteInternal(ProjectFile pf, string assetPath, bool actuallyWrite) { byte[] buff = JsonSerializer.SerializeToUtf8Bytes(this, ProjectFile.JsonOptions); string dir = null; if (actuallyWrite) { dir = Path.GetDirectoryName(assetPath); Directory.CreateDirectory(dir); using (FileStream fs = new FileStream(assetPath, FileMode.Create)) fs.Write(buff, 0, buff.Length); } // Compute hash manually here using (SHA1Managed sha1 = new SHA1Managed()) { Length = buff.Length; // Handle sprite frames for (int i = 0; i < TextureItems.Count; i++) { GMTextureItem item = TextureItems[i]; byte[] imgBuff; Bitmap imgBitmap; if (item.TexturePageID == -1) { imgBitmap = item._Bitmap; } else { imgBitmap = pf.Textures.GetTextureEntryBitmap(item, Width, Height); } imgBuff = imgBitmap.GetReadOnlyByteArray(); if (actuallyWrite) { using (FileStream fs = new FileStream(Path.Combine(dir, Name + "_" + i.ToString() + ".png"), FileMode.Create)) imgBitmap.Save(fs, System.Drawing.Imaging.ImageFormat.Png); } Length += imgBuff.Length; sha1.TransformBlock(imgBuff, 0, imgBuff.Length, null, 0); } if (SpecialInfo != null) { if (SpecialInfo.InternalBuffer != null && SpecialInfo.Buffer != null) { byte[] internalBufferArray = SpecialInfo.InternalBuffer.Memory.ToArray(); if (actuallyWrite) { using (FileStream fs = new FileStream(Path.Combine(dir, SpecialInfo.Buffer), FileMode.Create)) fs.Write(internalBufferArray, 0, internalBufferArray.Length); } Length += internalBufferArray.Length; sha1.TransformBlock(internalBufferArray, 0, internalBufferArray.Length, null, 0); } } // Save raw collision masks if (CollisionMask.RawMasks?.Count > 0) { for (int i = 0; i < CollisionMask.RawMasks.Count; i++) { byte[] mask = CollisionMask.RawMasks[i].Memory.ToArray(); if (actuallyWrite) { using (FileStream fs = new FileStream(Path.Combine(dir, Name + "_mask_" + i.ToString() + ".png"), FileMode.Create)) CollisionMasks.GetImageFromMask(Width, Height, mask).Save(fs, System.Drawing.Imaging.ImageFormat.Png); } Length += mask.Length; sha1.TransformBlock(mask, 0, mask.Length, null, 0); } } sha1.TransformFinalBlock(buff, 0, buff.Length); Hash = sha1.Hash; } return(null); }
public override void ConvertProject(ProjectFile pf) { var dataAssets = pf.DataHandle.GetChunk <GMChunkSPRT>().List; // Assemble dictionary of group names to actual Group classes Dictionary <string, Textures.Group> groupNames = new Dictionary <string, Textures.Group>(); foreach (var g in pf.Textures.TextureGroups) { groupNames[g.Name] = g; } List <GMSprite> newList = new List <GMSprite>(); for (int i = 0; i < pf.Sprites.Count; i++) { AssetSprite projectAsset = pf.Sprites[i].Asset; if (projectAsset == null) { // This asset was never converted newList.Add((GMSprite)pf.Sprites[i].DataAsset); continue; } // Add texture items to group if (!projectAsset.SeparateTextureGroup && projectAsset.TextureGroup != null && groupNames.TryGetValue(projectAsset.TextureGroup, out var group)) { foreach (var item in projectAsset.TextureItems) { if (item != null) { group.AddNewEntry(pf.Textures, item); } } } else { if (projectAsset.SeparateTextureGroup) { // Export each frame to a separate texture page foreach (var item in projectAsset.TextureItems) { if (item == null) { continue; } var g = new Textures.Group() { Dirty = true, Border = 0, AllowCrop = false, Name = $"__DS_AUTO_GEN_{projectAsset.Name}__{pf.Textures.TextureGroups.Count}", FillTGIN = false // Apparently }; g.AddNewEntry(pf.Textures, item); pf.Textures.TextureGroups.Add(g); } } else { // Make a new texture group for this var g = new Textures.Group() { Dirty = true, Border = 0, AllowCrop = false, Name = $"__DS_AUTO_GEN_{projectAsset.Name}__{pf.Textures.TextureGroups.Count}", FillTGIN = false // Apparently }; foreach (var item in projectAsset.TextureItems) { if (item != null) { g.AddNewEntry(pf.Textures, item); } } pf.Textures.TextureGroups.Add(g); } } CollisionMasks.Rect outbbox; GMSprite dataAsset = new GMSprite() { Name = pf.DataHandle.DefineString(projectAsset.Name), Transparent = projectAsset.Transparent, Smooth = projectAsset.Smooth, Preload = projectAsset.Preload, Width = projectAsset.Width, Height = projectAsset.Height, OriginX = projectAsset.OriginX, OriginY = projectAsset.OriginY, TextureItems = new GMRemotePointerList <GMTextureItem>(), CollisionMasks = CollisionMasks.GetMasksForSprite(pf, projectAsset, out outbbox) }; // Get collision mask info var colInfo = projectAsset.CollisionMask; if (colInfo.Left == null || colInfo.Top == null || colInfo.Right == null || colInfo.Bottom == null) { dataAsset.MarginLeft = outbbox.Left; dataAsset.MarginTop = outbbox.Top; dataAsset.MarginRight = outbbox.Right; dataAsset.MarginBottom = outbbox.Bottom; } else { dataAsset.MarginLeft = (int)colInfo.Left; dataAsset.MarginTop = (int)colInfo.Top; dataAsset.MarginRight = (int)colInfo.Right; dataAsset.MarginBottom = (int)colInfo.Bottom; } if ((int)colInfo.Mode < 0) { dataAsset.BBoxMode = (uint)(-(1 + (int)colInfo.Mode)); dataAsset.SepMasks = GMSprite.SepMaskType.Precise; } else { dataAsset.BBoxMode = (uint)colInfo.Mode; switch (colInfo.Type) { case AssetSprite.CollisionMaskInfo.MaskType.Rectangle: dataAsset.SepMasks = GMSprite.SepMaskType.AxisAlignedRect; break; case AssetSprite.CollisionMaskInfo.MaskType.RectangleWithRotation: dataAsset.SepMasks = GMSprite.SepMaskType.RotatedRect; break; case AssetSprite.CollisionMaskInfo.MaskType.Precise: case AssetSprite.CollisionMaskInfo.MaskType.Diamond: case AssetSprite.CollisionMaskInfo.MaskType.Ellipse: case AssetSprite.CollisionMaskInfo.MaskType.PrecisePerFrame: dataAsset.SepMasks = GMSprite.SepMaskType.Precise; break; } } // Actually add the texture items foreach (var item in projectAsset.TextureItems) { dataAsset.TextureItems.Add(item); } if (projectAsset.SpecialInfo != null) { var info = projectAsset.SpecialInfo; dataAsset.SpecialOrGMS2 = true; dataAsset.S_SpriteType = info.SpriteType; if (info.SpriteType != GMSprite.SpriteType.Normal) { dataAsset.S_Buffer = info.InternalBuffer; } if (info.GMS2PlaybackSpeed != null) { dataAsset.GMS2_PlaybackSpeed = (float)info.GMS2PlaybackSpeed; dataAsset.GMS2_PlaybackSpeedType = (GMSprite.AnimSpeedType)info.GMS2PlaybackSpeedType; } if (projectAsset.SpecialInfo.Sequence != null) { var seq = projectAsset.SpecialInfo.Sequence; var newSeq = new GMSequence() { Name = pf.DataHandle.DefineString(seq.Name), PlaybackType = GMSequence.PlaybackTypeEnum.Loop, PlaybackSpeed = dataAsset.GMS2_PlaybackSpeed, PlaybackSpeedType = dataAsset.GMS2_PlaybackSpeedType, Length = seq.Frames.Max(f => f.Position + f.Length), OriginX = dataAsset.OriginX, OriginY = dataAsset.OriginY, Volume = 1, BroadcastMessages = new(), Tracks = new() { new() { BuiltinName = 0, IsCreationTrack = false, Keyframes = new GMSequence.Track.ValueKeyframes() { List = new() }, ModelName = pf.DataHandle.DefineString("GMSpriteFramesTrack"), Name = pf.DataHandle.DefineString("frames"), OwnedResources = new(), OwnedResourceTypes = new(), Tags = new(), Tracks = new(), Traits = GMSequence.Track.TraitsEnum.Unknown1 } },