Added saving resources version on unpacking .res file, added packing .res file, closes #1

fonts_experiment 1.0.0.3
Valeriy Mironov 2017-11-29 04:42:21 +02:00
parent a6f36424e8
commit fa2cbebad2
24 changed files with 315 additions and 160 deletions

38
Resources/Extractor.cs Normal file
View File

@ -0,0 +1,38 @@
using System.Drawing.Imaging;
using System.IO;
using NLog;
using Resources.Models;
namespace Resources
{
public class Extractor
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly FileDescriptor _descriptor;
public Extractor(FileDescriptor descriptor)
{
_descriptor = descriptor;
}
public void Extract(string outputDirectory)
{
if (_descriptor.Version != null)
{
var fileName = Path.Combine(outputDirectory, "version");
using (var stream = File.OpenWrite(fileName))
using (var writer = new BinaryWriter(stream))
{
writer.Write(_descriptor.Version.Value);
}
}
for (var i = 0; i < _descriptor.Images.Count; i++)
{
var fileName = Path.Combine(outputDirectory, $"{i}.png");
Logger.Debug("Extracting {0}...", fileName);
_descriptor.Images[i].Save(fileName, ImageFormat.Png);
}
}
}
}

View File

@ -1,27 +1,20 @@
using System;
using System.Drawing;
using System.IO;
using NLog;
using Resources.Models;
namespace Resources
{
public class ResourcesFileReader
public class FileReader
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly BinaryReader _binaryReader;
private readonly Stream _stream;
public ResourcesFileReader(Stream stream)
public static FileDescriptor Read(Stream stream)
{
_stream = stream;
_binaryReader = new BinaryReader(stream);
}
var binaryReader = new BinaryReader(stream);
public Bitmap[] Read()
{
Logger.Trace("Reading resources header");
var header = ResourcesHeader.ReadFrom(_binaryReader);
var header = Header.ReadFrom(binaryReader);
Logger.Trace("Resources header was read:");
Logger.Trace("Signature: {0}, Version: {1}, ResourcesCount: {2}, IsValid: {3}",
header.Signature, header.Version, header.ResourcesCount, header.IsValid
@ -30,7 +23,11 @@ namespace Resources
if (!header.IsValid)
throw new ArgumentException("Invalid resources header");
return new ResourcesReader(_stream).Read(header.ResourcesCount);
return new FileDescriptor
{
Version = header.Version,
Images = new Reader(stream).Read(header.ResourcesCount)
};
}
}
}

41
Resources/FileWriter.cs Normal file
View File

@ -0,0 +1,41 @@
using System;
using System.IO;
using NLog;
using Resources.Models;
namespace Resources
{
public class FileWriter
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly BinaryWriter _binaryWriter;
private readonly Stream _stream;
public FileWriter(Stream stream)
{
_stream = stream;
_binaryWriter = new BinaryWriter(stream);
}
public void Write(FileDescriptor descriptor)
{
if (descriptor.Version == null)
throw new ArgumentException("Res file version required");
var header = new Header
{
ResourcesCount = (uint) descriptor.Images.Count,
Version = descriptor.Version.Value
};
Logger.Trace("Writing resources header...");
Logger.Trace("Signature: {0}, Version: {1}, ResourcesCount: {2}, IsValid: {3}",
header.Signature, header.Version, header.ResourcesCount, header.IsValid
);
header.WriteTo(_binaryWriter);
Logger.Trace("Writing images...");
new Writer(_stream).Write(descriptor.Images);
}
}
}

View File

@ -1,6 +1,6 @@
using System.Drawing;
namespace Resources.Dithering
namespace Resources.Image
{
public class ColorError
{

View File

@ -1,7 +1,7 @@
using System.Drawing;
using NLog;
namespace Resources.Dithering
namespace Resources.Image
{
public class FloydSteinbergDitherer
{

View File

@ -2,10 +2,11 @@
using System.Drawing;
using System.IO;
using NLog;
using Resources.Utils;
namespace Resources
namespace Resources.Image
{
public class ImageReader
public class Reader
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@ -18,7 +19,7 @@ namespace Resources
private bool _transparency;
private ushort _width;
public ImageReader(Stream stream)
public Reader(Stream stream)
{
_reader = new BinaryReader(stream);
}

View File

@ -4,11 +4,11 @@ using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using NLog;
using Resources.Dithering;
using Resources.Utils;
namespace Resources
namespace Resources.Image
{
public class ImageWriter
public class Writer
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static readonly byte[] Signature = {(byte) 'B', (byte) 'M', (byte) 'd', 0};
@ -24,7 +24,7 @@ namespace Resources
private ushort _transparency;
private ushort _width;
public ImageWriter(Stream stream)
public Writer(Stream stream)
{
_writer = new BinaryWriter(stream);
_palette = new List<Color>();

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Drawing;
namespace Resources.Models
{
public class FileDescriptor
{
public byte? Version { get; set; }
public List<Bitmap> Images { get; set; } = new List<Bitmap>();
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.IO;
using System.Text;
namespace Resources.Models
{
public class Header
{
public const int HeaderSize = 20;
private const string ResSignature = "HMRES";
public string Signature { get; private set; } = ResSignature;
public byte Version { get; set; }
public uint ResourcesCount { get; set; }
public bool IsValid => Signature == ResSignature;
public void WriteTo(BinaryWriter writer)
{
var buffer = new byte[HeaderSize];
for (var i = 0; i < buffer.Length; i++) buffer[i] = 0xff;
Encoding.ASCII.GetBytes(ResSignature).CopyTo(buffer, 0);
buffer[5] = Version;
BitConverter.GetBytes(ResourcesCount).CopyTo(buffer, 16);
writer.Write(buffer);
}
public static Header ReadFrom(BinaryReader reader)
{
var buffer = reader.ReadBytes(HeaderSize);
return new Header
{
Signature = Encoding.ASCII.GetString(buffer, 0, 5),
Version = buffer[5],
ResourcesCount = BitConverter.ToUInt32(buffer, 16)
};
}
}
}

View File

@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.2")]
[assembly: AssemblyFileVersion("1.0.0.2")]
[assembly: AssemblyVersion("1.0.0.3")]
[assembly: AssemblyFileVersion("1.0.0.3")]

View File

@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using NLog;
namespace Resources
{
public class ResourcesReader
public class Reader
{
private const int OffsetTableItemLength = 4;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@ -13,13 +14,13 @@ namespace Resources
private readonly BinaryReader _binaryReader;
private readonly Stream _stream;
public ResourcesReader(Stream stream)
public Reader(Stream stream)
{
_stream = stream;
_binaryReader = new BinaryReader(_stream);
}
public Bitmap[] Read(uint imagesTableLength)
public List<Bitmap> Read(uint imagesTableLength)
{
var offsetsTableLength = (int) (imagesTableLength * OffsetTableItemLength);
Logger.Trace("Reading resources offsets table with {0} elements ({1} bytes)",
@ -29,18 +30,20 @@ namespace Resources
var imagesOffset = _stream.Position;
Logger.Debug("Reading {0} images...", imagesTableLength);
var images = new Bitmap[imagesTableLength];
var images = new List<Bitmap>((int) imagesTableLength);
for (var i = 0; i < imagesTableLength; i++)
{
var imageOffset = BitConverter.ToUInt32(imagesOffsets, i * OffsetTableItemLength) + imagesOffset;
if (_stream.Position != imageOffset)
var imageOffset = BitConverter.ToUInt32(imagesOffsets, i * OffsetTableItemLength);
var realOffset = imageOffset + imagesOffset;
Logger.Trace("Image {0} offset is {1}...", i, imageOffset);
if (_stream.Position != realOffset)
{
var bytesGap = imageOffset - _stream.Position;
var bytesGap = realOffset - _stream.Position;
Logger.Warn("Found {0} bytes gap before resource number {1}", bytesGap, i);
_stream.Seek(imageOffset, SeekOrigin.Begin);
_stream.Seek(realOffset, SeekOrigin.Begin);
}
Logger.Debug("Reading image {0}...", i);
images[i] = new ImageReader(_stream).Read();
images.Add(new Image.Reader(_stream).Read());
}
return images;
}

View File

@ -44,20 +44,24 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="BitReader.cs" />
<Compile Include="BitWriter.cs" />
<Compile Include="Dithering\ColorError.cs" />
<Compile Include="Dithering\FloydSteinbergDitherer.cs" />
<Compile Include="ImageWriter.cs" />
<Compile Include="ResourcesExtractor.cs" />
<Compile Include="ResourcesHeader.cs" />
<Compile Include="ImageReader.cs" />
<Compile Include="ResourcesFileReader.cs" />
<Compile Include="Models\FileDescriptor.cs" />
<Compile Include="Utils\BitReader.cs" />
<Compile Include="Utils\BitWriter.cs" />
<Compile Include="Image\ColorError.cs" />
<Compile Include="Image\FloydSteinbergDitherer.cs" />
<Compile Include="Image\Writer.cs" />
<Compile Include="Writer.cs" />
<Compile Include="Extractor.cs" />
<Compile Include="FileWriter.cs" />
<Compile Include="Models\Header.cs" />
<Compile Include="Image\Reader.cs" />
<Compile Include="FileReader.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ResourcesReader.cs" />
<Compile Include="Reader.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -1,29 +0,0 @@
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using NLog;
namespace Resources
{
public class ResourcesExtractor
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly Bitmap[] _images;
public ResourcesExtractor(Bitmap[] images)
{
_images = images;
}
public void Extract(string outputDirectory)
{
for (var i = 0; i < _images.Length; i++)
{
var fileName = Path.Combine(outputDirectory, $"{i}.png");
Logger.Debug("Exporting {0}...", fileName);
_images[i].Save(fileName, ImageFormat.Png);
}
}
}
}

View File

@ -1,29 +0,0 @@
using System;
using System.IO;
using System.Text;
namespace Resources
{
public class ResourcesHeader
{
public const int HeaderSize = 20;
private const string ResHeaderSignature = "HMRES";
private readonly byte[] _header;
public ResourcesHeader(byte[] header)
{
_header = header;
}
public string Signature => Encoding.ASCII.GetString(_header, 0, 5);
public bool IsValid => Signature == ResHeaderSignature;
public uint Version => _header[5];
public uint ResourcesCount => BitConverter.ToUInt32(_header, 16);
public static ResourcesHeader ReadFrom(BinaryReader reader)
{
var buffer = reader.ReadBytes(HeaderSize);
return new ResourcesHeader(buffer);
}
}
}

View File

@ -1,7 +1,7 @@
using System;
using System.IO;
namespace Resources
namespace Resources.Utils
{
public class BitReader
{

View File

@ -1,7 +1,7 @@
using System;
using System.IO;
namespace Resources
namespace Resources.Utils
{
public class BitWriter
{

51
Resources/Writer.cs Normal file
View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using NLog;
namespace Resources
{
public class Writer
{
private const int OffsetTableItemLength = 4;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly Stream _stream;
public Writer(Stream stream)
{
_stream = stream;
}
public void Write(List<Bitmap> images)
{
var offsetsTable = new byte[images.Count * OffsetTableItemLength];
var encodedImages = new MemoryStream[images.Count];
var offset = (uint) 0;
for (var i = 0; i < images.Count; i++)
{
Logger.Trace("Image {0} offset is {1}...", i, offset);
var offsetBytes = BitConverter.GetBytes(offset);
offsetBytes.CopyTo(offsetsTable, i * OffsetTableItemLength);
var encodedImage = new MemoryStream();
Logger.Debug("Encoding image {0}...", i);
new Image.Writer(encodedImage).Write(images[i]);
offset += (uint) encodedImage.Length;
encodedImages[i] = encodedImage;
}
Logger.Trace("Writing images offsets table");
_stream.Write(offsetsTable, 0, offsetsTable.Length);
Logger.Debug("Writing images");
foreach (var encodedImage in encodedImages)
{
encodedImage.Seek(0, SeekOrigin.Begin);
encodedImage.CopyTo(_stream);
}
}
}
}

View File

@ -20,13 +20,9 @@ namespace WatchFace.Parser.Models
var buffer = new byte[HeaderSize];
for (var i = 0; i < buffer.Length; i++) buffer[i] = 0xff;
var signature = Encoding.ASCII.GetBytes(Signature);
var unknown = BitConverter.GetBytes(Unknown);
var parametersSize = BitConverter.GetBytes(ParametersSize);
signature.CopyTo(buffer, 0);
unknown.CopyTo(buffer, 32);
parametersSize.CopyTo(buffer, 36);
Encoding.ASCII.GetBytes(Signature).CopyTo(buffer, 0);
BitConverter.GetBytes(Unknown).CopyTo(buffer, 32);
BitConverter.GetBytes(ParametersSize).CopyTo(buffer, 36);
stream.Write(buffer, 0, HeaderSize);
}

View File

@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.2")]
[assembly: AssemblyFileVersion("1.0.0.2")]
[assembly: AssemblyVersion("1.0.0.3")]
[assembly: AssemblyFileVersion("1.0.0.3")]

View File

@ -2,7 +2,6 @@
using System.Drawing;
using System.IO;
using NLog;
using Resources;
using WatchFace.Parser.Models;
namespace WatchFace.Parser
@ -19,7 +18,7 @@ namespace WatchFace.Parser
}
public List<Parameter> Parameters { get; private set; }
public Bitmap[] Images { get; private set; }
public List<Bitmap> Images { get; private set; }
public void Read()
{
@ -47,7 +46,7 @@ namespace WatchFace.Parser
Logger.Trace("Watch face parameters locations were read:");
Parameters = ReadParameters(parametrsTableLength, parametersLocations);
Images = new ResourcesReader(_stream).Read((uint) imagesCount);
Images = new Resources.Reader(_stream).Read((uint) imagesCount);
}
private List<Parameter> ReadParameters(long coordinatesTableSize, ICollection<Parameter> parametersDescriptors)

View File

@ -104,6 +104,7 @@ namespace WatchFace.Parser.Utils
var fileName = Path.Combine(_imagesDirectory, $"{index}.png");
var newImageIndex = Images.Count;
Logger.Trace("Loading {0} image from file {1}", newImageIndex, fileName);
Images.Add((Bitmap) Image.FromFile(fileName));
_mapping[index] = newImageIndex;
return newImageIndex;

View File

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using NLog;
using Resources;
using WatchFace.Parser.Models;
using WatchFace.Parser.Utils;
@ -24,7 +22,10 @@ namespace WatchFace.Parser
public void Write(WatchFace watchFace)
{
Logger.Trace("Building parameters for watch face...");
var descriptor = ParametersConverter.Build(watchFace);
Logger.Trace("Encoding parameters...");
var encodedParameters = new Dictionary<byte, MemoryStream>(descriptor.Count);
foreach (var parameter in descriptor)
{
@ -34,9 +35,9 @@ namespace WatchFace.Parser
encodedParameters[parameter.Id] = memoryStream;
}
Logger.Trace("Encoding offsets and lengths...");
var parametersPositions = new List<Parameter>(descriptor.Count + 1);
var offset = (long) 0;
foreach (var encodedParameter in encodedParameters)
{
var encodedParameterId = encodedParameter.Key;
@ -58,6 +59,7 @@ namespace WatchFace.Parser
foreach (var parametersPosition in parametersPositions)
parametersPosition.Write(encodedParametersPositions);
Logger.Trace("Writing header...");
var header = new Header
{
ParametersSize = (uint) encodedParametersPositions.Length,
@ -65,43 +67,19 @@ namespace WatchFace.Parser
};
header.WriteTo(_stream);
Logger.Trace("Writing parameters offsets and lengths...");
encodedParametersPositions.Seek(0, SeekOrigin.Begin);
encodedParametersPositions.WriteTo(_stream);
Logger.Trace("Writing parameters...");
foreach (var encodedParameter in encodedParameters)
{
var stream = encodedParameter.Value;
stream.Seek(0, SeekOrigin.Begin);
stream.WriteTo(_stream);
}
WriteImages();
}
public void WriteImages()
{
var offsetsTable = new byte[_images.Count * 4];
var encodedImages = new MemoryStream[_images.Count];
var offset = (uint) 0;
for (var i = 0; i < _images.Count; i++)
{
var offsetBytes = BitConverter.GetBytes(offset);
offsetBytes.CopyTo(offsetsTable, i * 4);
var encodedImage = new MemoryStream();
Logger.Debug("Writing image {0}...", i);
new ImageWriter(encodedImage).Write(_images[i]);
offset += (uint) encodedImage.Length;
encodedImages[i] = encodedImage;
}
_stream.Write(offsetsTable, 0, offsetsTable.Length);
foreach (var encodedImage in encodedImages)
{
encodedImage.Seek(0, SeekOrigin.Begin);
encodedImage.CopyTo(_stream);
}
Logger.Trace("Writing images...");
new Resources.Writer(_stream).Write(_images);
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Newtonsoft.Json;
@ -6,8 +7,10 @@ using NLog;
using NLog.Config;
using NLog.Targets;
using Resources;
using WatchFace.Parser;
using Resources.Models;
using WatchFace.Parser.Utils;
using Reader = WatchFace.Parser.Reader;
using Writer = WatchFace.Parser.Writer;
namespace WatchFace
{
@ -22,12 +25,13 @@ namespace WatchFace
if (args.Length == 0 || args[0] == null)
{
Console.WriteLine(
"{0}.exe unpacks and packs Amazfit Bip downloadable watch faces and unpacks res files.", AppName);
"{0}.exe unpacks and packs Amazfit Bip downloadable watch faces and resource files.", AppName);
Console.WriteLine();
Console.WriteLine("Usage examples:");
Console.WriteLine(" {0}.exe watchface.bin - unpacks watchface images and config", AppName);
Console.WriteLine(" {0}.exe watchface.res - unpacks resource file images", AppName);
Console.WriteLine(" {0}.exe watchface.json - packs config and referenced images to bin file", AppName);
Console.WriteLine(" {0}.exe watchface.bin - unpacks watchface images and config", AppName);
Console.WriteLine(" {0}.exe watchface.json - packs config and referenced images to bin file", AppName);
Console.WriteLine(" {0}.exe mili_chaohu.res - unpacks resource file images", AppName);
Console.WriteLine(" {0}.exe mili_chaohu - packs folder content to res file", AppName);
return;
}
@ -36,9 +40,24 @@ namespace WatchFace
foreach (var inputFileName in args)
{
if (!File.Exists(inputFileName))
var isDirectory = Directory.Exists(inputFileName);
var isFile = File.Exists(inputFileName);
if (!isDirectory && !isFile)
{
Console.WriteLine("File '{0}' doesn't exists.", inputFileName);
Console.WriteLine("File or directory '{0}' doesn't exists.", inputFileName);
continue;
}
if (isDirectory)
{
Console.WriteLine("Processing directory '{0}'", inputFileName);
try
{
PackResources(inputFileName);
}
catch (Exception e)
{
Logger.Fatal(e);
}
continue;
}
@ -105,23 +124,57 @@ namespace WatchFace
if (watchFace == null) return;
Logger.Debug("Exporting resources to '{0}'", outputDirectory);
new ResourcesExtractor(reader.Images).Extract(outputDirectory);
var reDescriptor = new FileDescriptor {Images = reader.Images};
new Extractor(reDescriptor).Extract(outputDirectory);
ExportConfig(watchFace, Path.Combine(outputDirectory, $"{baseName}.json"));
}
private static void PackResources(string inputDirectory)
{
var outputDirectory = Path.GetDirectoryName(inputDirectory);
var baseName = Path.GetFileName(inputDirectory);
var outputFileName = Path.Combine(outputDirectory, $"{baseName}_packed.res");
var logFileName = Path.Combine(outputDirectory, $"{baseName}_packed.log");
SetupLogger(logFileName);
var versionFileName = Path.Combine(inputDirectory, "version");
var resDescriptor = new FileDescriptor();
using (var stream = File.OpenRead(versionFileName))
using (var reader = new BinaryReader(stream))
{
resDescriptor.Version = reader.ReadByte();
}
var i = 0;
var images = new List<Bitmap>();
while (true)
{
var fileName = Path.Combine(inputDirectory, $"{i}.png");
if (!File.Exists(fileName)) break;
images.Add((Bitmap) Image.FromFile(fileName));
i++;
}
resDescriptor.Images = images;
using (var stream = File.OpenWrite(outputFileName))
{
new FileWriter(stream).Write(resDescriptor);
}
}
private static void UnpackResources(string inputFileName)
{
var outputDirectory = CreateOutputDirectory(inputFileName);
var baseName = Path.GetFileNameWithoutExtension(inputFileName);
SetupLogger(Path.Combine(outputDirectory, $"{baseName}.log"));
Bitmap[] images;
FileDescriptor resDescriptor;
using (var stream = File.OpenRead(inputFileName))
{
images = new ResourcesFileReader(stream).Read();
resDescriptor = FileReader.Read(stream);
}
new ResourcesExtractor(images).Extract(outputDirectory);
new Extractor(resDescriptor).Extract(outputDirectory);
}
private static void WriteWatchFace(string outputFileName, string imagesDirectory, Parser.WatchFace watchFace)

View File

@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.2")]
[assembly: AssemblyFileVersion("1.0.0.2")]
[assembly: AssemblyVersion("1.0.0.3")]
[assembly: AssemblyFileVersion("1.0.0.3")]