-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* A lot of work to-do here.
- Loading branch information
PaulusParssinen
committed
Mar 3, 2020
1 parent
5ce332c
commit 027c996
Showing
14 changed files
with
319 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -286,4 +286,3 @@ __pycache__/ | |
*.btm.cs | ||
*.odx.cs | ||
*.xsd.cs | ||
/Shockky.Sandbox |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,290 @@ | ||
using System; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Drawing; | ||
using System.Diagnostics; | ||
using System.Collections.Generic; | ||
using System.Runtime.InteropServices; | ||
using System.Text.RegularExpressions; | ||
|
||
using Shockky.Chunks; | ||
using Shockky.Chunks.Cast; | ||
|
||
using System.CommandLine; | ||
using System.CommandLine.Invocation; | ||
|
||
using SixLabors.ImageSharp; | ||
using SixLabors.ImageSharp.PixelFormats; | ||
|
||
namespace Shockky.Sandbox | ||
{ | ||
class Program | ||
{ | ||
static int Main(string[] args) | ||
{ | ||
Console.Title = "Shockky.Sandbox"; | ||
Console.WriteLine("Shockky.Sandbox"); | ||
Console.WriteLine(); | ||
|
||
//TODO: Verbose and Quiet levels, and rest of the resources of course | ||
var rootCommand = new RootCommand() | ||
{ | ||
new Argument<IEnumerable<FileInfo>>("input") | ||
{ | ||
Arity = ArgumentArity.OneOrMore, | ||
Description = "Director movie (.dir, .dxt, .dcr) or external cast (.cst, .cxt, .cct) file(s)." | ||
}.ExistingOnly(), | ||
|
||
new Option<bool>("--images", | ||
getDefaultValue: () => true, | ||
description: "Extract all (with bit depth 8) bitmaps from the file."), | ||
|
||
new Option<DirectoryInfo>("--output", | ||
getDefaultValue: () => new DirectoryInfo("Output/"), | ||
description: "Directory for the extracted resources").LegalFilePathsOnly() | ||
}; | ||
rootCommand.Handler = CommandHandler.Create<IEnumerable<FileInfo>, bool, DirectoryInfo>(HandleExtractCommand); | ||
|
||
return rootCommand.Invoke(args); | ||
} | ||
|
||
private static IReadOnlyDictionary<int, System.Drawing.Color[]> ReadPalettes() | ||
{ | ||
static System.Drawing.Color[] ReadPalette(string fileName) | ||
{ | ||
using var fs = File.OpenRead(fileName); | ||
using var input = new BinaryReader(fs, Encoding.ASCII); | ||
|
||
input.ReadChars(4); | ||
input.ReadInt32(); | ||
|
||
input.ReadChars(4); | ||
|
||
input.ReadChars(4); | ||
input.ReadInt32(); | ||
input.ReadInt16(); | ||
|
||
System.Drawing.Color[] colors = new System.Drawing.Color[input.ReadInt16()]; | ||
for (int i = 0; i < colors.Length; i++) | ||
{ | ||
byte r = input.ReadByte(); | ||
byte g = input.ReadByte(); | ||
byte b = input.ReadByte(); | ||
|
||
colors[i] = System.Drawing.Color.FromArgb(r, g, b); | ||
|
||
input.ReadByte(); | ||
} | ||
return colors; | ||
} | ||
|
||
return new Dictionary<int, System.Drawing.Color[]> | ||
{ | ||
{ -1, ReadPalette("Palettes/mac.pal") }, | ||
{ -2, ReadPalette("Palettes/rainbow.pal") }, | ||
{ -3, ReadPalette("Palettes/grey.pal") }, | ||
{ -4, ReadPalette("Palettes/pastels.pal") }, | ||
{ -5, ReadPalette("Palettes/vivid.pal") }, | ||
{ -6, ReadPalette("Palettes/ntsc.pal") }, | ||
{ -7, ReadPalette("Palettes/metallic.pal") }, | ||
{ -8, ReadPalette("Palettes/web216.pal") }, | ||
{ -9, null }, //TODO: "Palettes/VGA.pal" | ||
{ -101, ReadPalette("Palettes/windir4.pal") }, | ||
{ -102, ReadPalette("Palettes/win.pal") } | ||
}; | ||
} | ||
|
||
private static int HandleExtractCommand(IEnumerable<FileInfo> input, bool images, DirectoryInfo output) | ||
{ | ||
if (!images) | ||
return 0; | ||
|
||
output.Create(); | ||
|
||
//Load the built-in system palettes | ||
IReadOnlyDictionary<int, System.Drawing.Color[]> systemPalettes = ReadPalettes(); | ||
|
||
foreach (var file in input) | ||
{ | ||
//TODO: Seperate different resource types. | ||
DirectoryInfo fileOutputDirectory = output.CreateSubdirectory(file.Name); | ||
|
||
Console.Write($"Disassembling file \"{file.Name}\".."); | ||
|
||
using var shockwaveFile = new ShockwaveFile(file.FullName); | ||
shockwaveFile.Disassemble(); | ||
|
||
Console.WriteLine("Done!"); | ||
|
||
List<(CastMemberPropertiesChunk Member, ChunkItem Media)> memberMedia = new List<(CastMemberPropertiesChunk, ChunkItem)>(); | ||
|
||
var associationTable = shockwaveFile.Chunks | ||
.FirstOrDefault(c => c.Kind == ChunkKind.KEYPointer) as AssociationTableChunk; | ||
|
||
var castAssociationTable = shockwaveFile.Chunks | ||
.FirstOrDefault(c => c.Kind == ChunkKind.CASPointer) as CastAssociationTableChunk; | ||
|
||
if (associationTable == null) | ||
{ | ||
Console.WriteLine($"Chunk \"{nameof(AssociationTableChunk)}\" was not found!"); | ||
continue; | ||
} | ||
|
||
if (castAssociationTable == null) | ||
{ | ||
Console.WriteLine($"Chunk \"{nameof(CastAssociationTableChunk)}\" was not found!"); | ||
continue; | ||
} | ||
Console.Write("Extracting resources.."); | ||
|
||
//TODO: Report progress, try some of those System.CommandLine goodies? | ||
|
||
//Build a list of the cast member-media pairs. | ||
foreach (int memberId in castAssociationTable.Members) | ||
{ | ||
if (memberId == 0) continue; | ||
|
||
var castMemberChunk = shockwaveFile[memberId] as CastMemberPropertiesChunk; | ||
|
||
var mediaEntries = associationTable.CastEntries.Where(e => e.OwnerId == memberId); | ||
|
||
foreach (var mediaEntry in mediaEntries) | ||
{ | ||
//TODO: filter mediaEntry.Kind here to extract only wanted resources | ||
|
||
memberMedia.Add((castMemberChunk, shockwaveFile[mediaEntry.Id])); | ||
} | ||
} | ||
|
||
//TODO: DIB and others.. | ||
foreach (var (member, media) in memberMedia.Where(entry => entry.Media?.Kind == ChunkKind.BITD)) | ||
{ | ||
var bitmapChunk = media as BitmapChunk; | ||
var bitmapProperties = member?.Properties as BitmapCastProperties; | ||
|
||
if (bitmapProperties == null) continue; | ||
|
||
string outputFileName = CoerceValidFileName(member.Common?.Name) | ||
?? $"NONAME-{member.Header.Id}-{media.Header.Id}"; | ||
|
||
int paletteIndex = bitmapProperties.Palette - 1; //castMemRef | ||
|
||
if (paletteIndex >= 0 && paletteIndex < castAssociationTable.Members.Length) | ||
{ | ||
//TODO: Research why these safety checks still fail for some files.. CastMemRef's seems to point to non palette members? | ||
|
||
int paletteMemberChunkId = castAssociationTable.Members[paletteIndex]; | ||
|
||
if (shockwaveFile[paletteMemberChunkId] is CastMemberPropertiesChunk paletteMember) | ||
{ | ||
if (memberMedia.FirstOrDefault(entry => entry.Member == paletteMember).Media is PaletteChunk paletteChunk) | ||
{ | ||
bitmapChunk.PopulateMedia(bitmapProperties); | ||
if (TryExtractBitmapResource(fileOutputDirectory, outputFileName, bitmapChunk, paletteChunk.Colors)) | ||
{ | ||
Console.Write('.'); | ||
continue; | ||
} | ||
} | ||
} | ||
} | ||
else if (systemPalettes.TryGetValue(paletteIndex, out System.Drawing.Color[] palette)) | ||
{ | ||
bitmapChunk.PopulateMedia(bitmapProperties); | ||
if (TryExtractBitmapResource(fileOutputDirectory, outputFileName, bitmapChunk, palette)) | ||
{ | ||
Console.Write('.'); | ||
continue; | ||
} | ||
} | ||
Console.Write('x'); | ||
} | ||
Console.WriteLine(" Done!"); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
//TODO: Look more into ImageSharp, could offer some helpful tools to do this | ||
private static bool TryExtractBitmapResource(DirectoryInfo outputDirectory, string name, BitmapChunk bitmap, System.Drawing.Color[] palette) | ||
{ | ||
//TODO: Properly render flags etc. TrimWhitespace for example uses flood fill apparently so that could be fun. | ||
|
||
Span<byte> buffer = bitmap.Data.AsSpan(); | ||
|
||
int width = bitmap.Width < bitmap.TotalWidth ? bitmap.Width : bitmap.TotalWidth; //TODO: This is wrong way | ||
|
||
using var image = new Image<Bgra32>(bitmap.Width, bitmap.Height); | ||
for (int y = 0; y < bitmap.Height; y++) | ||
{ | ||
Span<byte> row = buffer.Slice(y * bitmap.TotalWidth, bitmap.TotalWidth); | ||
|
||
if (bitmap.BitDepth == 32) //TODO: Can't get this right yet, probably wrong PixelFormat | ||
{ | ||
return false; | ||
|
||
//Span<Bgra32> pixels = MemoryMarshal.Cast<byte, Bgra32>(row); | ||
// | ||
//for (int x = 0; x < bitmap.Width; x++) | ||
//{ | ||
// image[x, y] = pixels[x]; | ||
//} | ||
} | ||
else if (bitmap.BitDepth == 4) | ||
{ | ||
return false; | ||
|
||
//for (int x = 0; x < width; x++) | ||
//{ | ||
// System.Drawing.Color pixelColor = palette[row[x] >> 4]; | ||
// System.Drawing.Color secondPixelColor = palette[row[x] & 0xF]; | ||
// | ||
// //image[x, y] = new Bgra32(pixelColor.R, pixelColor.G, pixelColor.B); | ||
//} | ||
} | ||
else | ||
{ | ||
for (int x = 0; x < width; x++) | ||
{ | ||
System.Drawing.Color pixelColor = palette[row[x]]; | ||
image[x, y] = new Bgra32(pixelColor.R, pixelColor.G, pixelColor.B); | ||
} | ||
} | ||
} | ||
|
||
using var fs = File.Create(Path.GetFullPath(name + ".png", outputDirectory.FullName)); | ||
image.SaveAsPng(fs); | ||
|
||
return true; | ||
} | ||
|
||
/// <summary> | ||
/// Strip illegal chars and reserved words from a candidate filename (should not include the directory path) | ||
/// </summary> | ||
/// <remarks> | ||
/// http://stackoverflow.com/questions/309485/c-sharp-sanitize-file-name | ||
/// </remarks> | ||
public static string CoerceValidFileName(string filename) | ||
{ | ||
var invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars())); | ||
var invalidReStr = string.Format(@"[{0}]+", invalidChars); | ||
|
||
var reservedWords = new[] | ||
{ | ||
"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", | ||
"COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", | ||
"LPT5", "LPT6", "LPT7", "LPT8", "LPT9" | ||
}; | ||
|
||
var sanitisedNamePart = Regex.Replace(filename, invalidReStr, "_"); | ||
foreach (var reservedWord in reservedWords) | ||
{ | ||
var reservedWordPattern = string.Format("^{0}\\.", reservedWord); | ||
sanitisedNamePart = Regex.Replace(sanitisedNamePart, reservedWordPattern, "_reservedWord_.", RegexOptions.IgnoreCase); | ||
} | ||
|
||
return sanitisedNamePart; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>netcoreapp3.1</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Shockky\Shockky.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0007" /> | ||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20104.2" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Include="Palettes\*" CopyToOutputDirectory="PreserveNewest" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters