-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #107 from Xeeynamo/feature/bar-editor-improvements
BAR editor improvements
- Loading branch information
Showing
24 changed files
with
938 additions
and
293 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 |
---|---|---|
@@ -0,0 +1,116 @@ | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
|
||
namespace OpenKh.Command.Bar | ||
{ | ||
public static class Core | ||
{ | ||
internal class BarRoot | ||
{ | ||
[JsonProperty] public string OriginalFileName { get; set; } | ||
|
||
[JsonProperty] public List<BarDesc> Entries { get; set; } | ||
} | ||
|
||
internal class BarDesc | ||
{ | ||
[JsonProperty] public string FileName { get; set; } | ||
[JsonProperty] public string InternalName { get; set; } | ||
[JsonProperty] public int TypeId { get; set; } | ||
[JsonProperty] public int LinkIndex { get; set; } | ||
[JsonIgnore] public Stream Stream { get; set; } | ||
|
||
public override string ToString() => FileName; | ||
} | ||
|
||
private const string InvalidBarText = "The specified file is not a BAR file."; | ||
internal static readonly Exception InvalidBarFileException = new InvalidDataException(InvalidBarText); | ||
|
||
public static List<Kh2.Bar.Entry> ReadEntries(string fileName) | ||
{ | ||
using var stream = File.OpenRead(fileName); | ||
if (!Kh2.Bar.IsValid(stream)) | ||
throw InvalidBarFileException; | ||
|
||
stream.Position = 0; | ||
return Kh2.Bar.Read(stream); | ||
} | ||
|
||
public static void ExportProject(string inputFileName, string outputFolder, bool suppress = false) | ||
{ | ||
var barEntries = ReadEntries(inputFileName); | ||
var project = new BarRoot | ||
{ | ||
OriginalFileName = Path.GetFileName(inputFileName), | ||
Entries = barEntries | ||
.Select(x => new BarDesc | ||
{ | ||
FileName = $"{x.Name}.{Helpers.GetSuggestedExtension(x.Type)}", | ||
InternalName = x.Name, | ||
TypeId = (int)x.Type, | ||
LinkIndex = x.Index, | ||
Stream = x.Stream, | ||
}) | ||
.ToList() | ||
}; | ||
|
||
foreach (var entryGroup in project.Entries | ||
.Where(x => x.LinkIndex == 0) | ||
.GroupBy(x => $"{x.InternalName}_{x.TypeId}")) | ||
{ | ||
var items = entryGroup.ToArray(); | ||
if (items.Length > 1) | ||
{ | ||
for (var i = 0; i < items.Length; i++) | ||
{ | ||
var ext = Helpers.GetSuggestedExtension((Kh2.Bar.EntryType)items[0].TypeId); | ||
items[i].FileName = $"{items[0].InternalName}_{i}.{ext}"; | ||
} | ||
} | ||
} | ||
|
||
if (!suppress) | ||
{ | ||
var fileNameWithExt = Path.GetFileName(inputFileName); | ||
var projectFileName = Path.Combine(outputFolder, $"{fileNameWithExt}.json"); | ||
if (!Directory.Exists(outputFolder)) | ||
Directory.CreateDirectory(outputFolder); | ||
|
||
File.WriteAllText(projectFileName, JsonConvert.SerializeObject(project, Formatting.Indented)); | ||
} | ||
|
||
foreach (var entry in project.Entries.Where(x => x.LinkIndex == 0)) | ||
{ | ||
var outputFileName = Path.Combine(outputFolder, entry.FileName); | ||
|
||
using var stream = File.Create(outputFileName); | ||
entry.Stream.Position = 0; | ||
entry.Stream.CopyTo(stream); | ||
} | ||
} | ||
|
||
public static IEnumerable<Kh2.Bar.Entry> ImportProject(string inputProjectName, out string originalFileName) | ||
{ | ||
var baseDirectory = Path.GetDirectoryName(inputProjectName); | ||
var project = JsonConvert.DeserializeObject<BarRoot>(File.ReadAllText(inputProjectName)); | ||
originalFileName = project.OriginalFileName; | ||
|
||
var streams = project.Entries | ||
.Where(x => x.LinkIndex == 0) | ||
.ToDictionary(x => x.FileName, | ||
x => File.OpenRead(Path.Combine(baseDirectory, x.FileName))); | ||
|
||
return project.Entries | ||
.Select(x => new Kh2.Bar.Entry | ||
{ | ||
Name = x.InternalName, | ||
Type = (Kh2.Bar.EntryType)x.TypeId, | ||
Index = x.LinkIndex, | ||
Stream = x.LinkIndex == 0 ? streams[x.FileName] : null | ||
}); | ||
} | ||
} | ||
} |
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,65 @@ | ||
using static OpenKh.Kh2.Bar; | ||
using System.Collections.Generic; | ||
|
||
namespace OpenKh.Command.Bar | ||
{ | ||
public class Helpers | ||
{ | ||
private static readonly string DefaultExtension = "bin"; | ||
|
||
private static readonly Dictionary<EntryType, string> SuggestedExtensions = | ||
new Dictionary<EntryType, string> | ||
{ | ||
[EntryType.Dummy] = "dummy", | ||
[EntryType.Binary] = "bin", | ||
[EntryType.List] = "list", | ||
[EntryType.Ai] = "ai", | ||
[EntryType.Model] = "model", | ||
[EntryType.MeshOcclusion] = "doct", | ||
[EntryType.MapCollision] = "coct", | ||
[EntryType.ModelTexture] = "modeltexture", | ||
[EntryType.Dpx] = "dpx", | ||
[EntryType.AnimationData] = "animation", | ||
[EntryType.Texture] = "texture", | ||
[EntryType.CameraCollision] = "coctcamera", | ||
[EntryType.SpawnPoint] = "spawnpoint", | ||
[EntryType.SpawnScript] = "spawnscript", | ||
[EntryType.MapColorDiffuse] = "colordiffuse", | ||
[EntryType.LightData] = "coctlight", | ||
[EntryType.Anb] = "Anb", | ||
[EntryType.Bar] = "bar", | ||
[EntryType.Pax] = "pax", | ||
[EntryType.MapCollision2] = "coctmap", | ||
[EntryType.AnimationLimit] = "animlimit", | ||
[EntryType.Unknown21] = "unk21", | ||
[EntryType.AnimationLoader] = "animload", | ||
[EntryType.ModelCollision] = "coctmodel", | ||
[EntryType.Imgd] = "imd", | ||
[EntryType.Seqd] = "sequence", | ||
[EntryType.Layout] = "layout", | ||
[EntryType.Imgz] = "imz", | ||
[EntryType.AnimationMap] = "mapanim", | ||
[EntryType.Seb] = "seb", | ||
[EntryType.Wd] = "wd", | ||
[EntryType.Unknown33] = "unk33", | ||
[EntryType.IopVoice] = "iopvoice", | ||
[EntryType.RawBitmap] = "rgb", | ||
[EntryType.MemoryCard] = "memcard", | ||
[EntryType.WrappedCollisionData] = "coctwrapped", | ||
[EntryType.Unknown39] = "unk39", | ||
[EntryType.Unknown40] = "unk40", | ||
[EntryType.Unknown41] = "unk41", | ||
[EntryType.Minigame] = "minigame", | ||
[EntryType.JimiData] = "jimidata", | ||
[EntryType.Progress] = "progress", | ||
[EntryType.Synthesis] = "synthesis", | ||
[EntryType.BarUnknown] = "bar", | ||
[EntryType.Vibration] = "vibration", | ||
[EntryType.Vag] = "vag", | ||
|
||
}; | ||
|
||
public static string GetSuggestedExtension(EntryType type) => | ||
SuggestedExtensions.TryGetValue(type, out var ext) ? ext : DefaultExtension; | ||
} | ||
} |
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,17 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>netcoreapp3.1</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="3.0.0" /> | ||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\OpenKh.Kh2\OpenKh.Kh2.csproj" /> | ||
</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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
using McMaster.Extensions.CommandLineUtils; | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.ComponentModel.DataAnnotations; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
|
||
namespace OpenKh.Command.Bar | ||
{ | ||
[Command("OpenKh.Command.Bar")] | ||
[VersionOptionFromMember("--version", MemberName = nameof(GetVersion))] | ||
[Subcommand( | ||
typeof(UnpackCommand), | ||
typeof(PackCommand), | ||
typeof(ListCommand))] | ||
class Program | ||
{ | ||
private const string InputProjectDesc = "BAR project file (eg. P_EX100.json)."; | ||
private const string InputBarDesc = "Kingdom Hearts II BAR file."; | ||
private const string OutputBarDesc = "Name of the BAR file that will be created."; | ||
private const string OutputDirDesc = "Path where the content will be extracted."; | ||
private const string SuppressProjectCreationDesc = "Do not generate a project when unpacking."; | ||
|
||
static int Main(string[] args) | ||
{ | ||
try | ||
{ | ||
return CommandLineApplication.Execute<Program>(args); | ||
} | ||
catch (FileNotFoundException e) | ||
{ | ||
Console.WriteLine($"The file {e.FileName} cannot be found. The program will now exit."); | ||
return 2; | ||
} | ||
catch (Exception e) | ||
{ | ||
Console.WriteLine($"FATAL ERROR: {e.Message}\n{e.StackTrace}"); | ||
return -1; | ||
} | ||
} | ||
|
||
protected int OnExecute(CommandLineApplication app) | ||
{ | ||
app.ShowHelp(); | ||
return 1; | ||
} | ||
|
||
private static string GetVersion() | ||
=> typeof(Program).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion; | ||
|
||
private static List<Kh2.Bar.Entry> ReadEntries(string fileName) | ||
{ | ||
using var stream = File.OpenRead(fileName); | ||
if (!Kh2.Bar.IsValid(stream)) | ||
throw Core.InvalidBarFileException; | ||
|
||
stream.Position = 0; | ||
return Kh2.Bar.Read(stream); | ||
} | ||
|
||
[Command(Description = "Unpack the content of a BAR file and generate a project")] | ||
private class UnpackCommand | ||
{ | ||
[Required] | ||
[FileExists] | ||
[Argument(0, Description = InputBarDesc)] | ||
public string InputBar { get; set; } | ||
|
||
[Option(CommandOptionType.SingleValue, Description = OutputDirDesc, ShortName = "o", LongName = "output")] | ||
public string OutputDir { get; set; } | ||
|
||
[Option(CommandOptionType.NoValue, Description = SuppressProjectCreationDesc, ShortName = "s", LongName = "skip")] | ||
public bool SuppressProjectCreation { get; set; } | ||
|
||
protected int OnExecute(CommandLineApplication app) | ||
{ | ||
if (string.IsNullOrEmpty(OutputDir)) | ||
{ | ||
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(InputBar); | ||
OutputDir = Path.Combine(Path.GetDirectoryName(InputBar), fileNameWithoutExt); | ||
} | ||
|
||
Core.ExportProject(InputBar, OutputDir, SuppressProjectCreation); | ||
|
||
return 0; | ||
} | ||
} | ||
|
||
[Command(Description = "Repack a BAR from its project file")] | ||
private class PackCommand | ||
{ | ||
[Required] | ||
[FileExists] | ||
[Argument(0, Description = InputProjectDesc)] | ||
public string InputProject { get; set; } | ||
|
||
[Option(CommandOptionType.SingleValue, Description = OutputBarDesc, ShortName = "o", LongName = "output")] | ||
public string OutputFile { get; set; } | ||
|
||
protected int OnExecute(CommandLineApplication app) | ||
{ | ||
var baseDirectory = Path.GetDirectoryName(InputProject); | ||
var bar = Core.ImportProject(InputProject, out var originalFileName); | ||
|
||
if (string.IsNullOrEmpty(OutputFile)) | ||
OutputFile = Path.Combine(baseDirectory, originalFileName); | ||
|
||
if (!File.Exists(OutputFile) && Directory.Exists(OutputFile) && | ||
File.GetAttributes(OutputFile).HasFlag(FileAttributes.Directory)) | ||
OutputFile = Path.Combine(OutputFile, originalFileName); | ||
|
||
using var outputStream = File.Create(OutputFile); | ||
Kh2.Bar.Write(outputStream, bar); | ||
|
||
foreach (var entry in bar) | ||
entry.Stream?.Dispose(); | ||
|
||
return 0; | ||
} | ||
} | ||
|
||
[Command(Description = "Print content of a BAR file")] | ||
private class ListCommand | ||
{ | ||
[Required] | ||
[FileExists] | ||
[Argument(0, Description = InputBarDesc)] | ||
public string InputBar { get; set; } | ||
|
||
protected int OnExecute(CommandLineApplication app) | ||
{ | ||
using var stream = File.OpenRead(InputBar); | ||
foreach (var entry in Kh2.Bar.Read(stream)) | ||
Console.WriteLine($"{entry.Name}, {entry.Type}, {entry.Index}"); | ||
|
||
return 0; | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.