mirror of
https://github.com/crskycode/GARbro.git
synced 2024-11-27 23:54:02 +08:00
277 lines
11 KiB
C#
277 lines
11 KiB
C#
//! \file ArcWILL.cs
|
|
//! \date Fri Oct 31 13:37:11 2014
|
|
//! \brief Will ARC archive format implementation.
|
|
//
|
|
// Copyright (C) 2014-2015 by morkt
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel.Composition;
|
|
using System.IO;
|
|
using GameRes.Formats.Properties;
|
|
using GameRes.Formats.Strings;
|
|
|
|
namespace GameRes.Formats.Will
|
|
{
|
|
internal class ExtRecord
|
|
{
|
|
public string Extension;
|
|
public int FileCount;
|
|
public uint DirOffset;
|
|
}
|
|
|
|
public class ArcOptions : ResourceOptions
|
|
{
|
|
public int NameLength { get; set; }
|
|
}
|
|
|
|
[Export(typeof(ArchiveFormat))]
|
|
public class ArcOpener : ArchiveFormat
|
|
{
|
|
public override string Tag { get { return "ARC/Will"; } }
|
|
public override string Description { get { return "Will Co. game engine resource archive"; } }
|
|
public override uint Signature { get { return 0; } }
|
|
public override bool IsHierarchic { get { return false; } }
|
|
public override bool CanCreate { get { return true; } }
|
|
|
|
ArcOpener ()
|
|
{
|
|
Extensions = new string[] { "arc" };
|
|
}
|
|
|
|
public override ArcFile TryOpen (ArcView file)
|
|
{
|
|
int ext_count = file.View.ReadInt32 (0);
|
|
if (ext_count <= 0 || ext_count > 0xff)
|
|
return null;
|
|
|
|
uint dir_offset = 4;
|
|
var ext_list = new List<ExtRecord> (ext_count);
|
|
for (int i = 0; i < ext_count; ++i)
|
|
{
|
|
string ext = file.View.ReadString (dir_offset, 4).ToLowerInvariant();
|
|
int count = file.View.ReadInt32 (dir_offset+4);
|
|
uint offset = file.View.ReadUInt32 (dir_offset+8);
|
|
if (count <= 0 || count > 0xffff || offset <= dir_offset || offset > file.MaxOffset)
|
|
return null;
|
|
ext_list.Add (new ExtRecord { Extension = ext, FileCount = count, DirOffset = offset });
|
|
dir_offset += 12;
|
|
}
|
|
var dir = ReadFileList (file, ext_list, 9);
|
|
if (null == dir)
|
|
dir = ReadFileList (file, ext_list, 13);
|
|
if (null == dir)
|
|
return null;
|
|
return new ArcFile (file, this, dir);
|
|
}
|
|
|
|
List<Entry> ReadFileList (ArcView file, IEnumerable<ExtRecord> ext_list, uint name_size)
|
|
{
|
|
var dir = new List<Entry>();
|
|
foreach (var ext in ext_list)
|
|
{
|
|
dir.Capacity = dir.Count + ext.FileCount;
|
|
uint dir_offset = ext.DirOffset;
|
|
for (int i = 0; i < ext.FileCount; ++i)
|
|
{
|
|
string name = file.View.ReadString (dir_offset, name_size);
|
|
if (string.IsNullOrEmpty (name))
|
|
return null;
|
|
name = name.ToLowerInvariant()+'.'+ext.Extension;
|
|
var entry = FormatCatalog.Instance.Create<Entry> (name);
|
|
entry.Size = file.View.ReadUInt32 (dir_offset+name_size);
|
|
entry.Offset = file.View.ReadUInt32 (dir_offset+name_size+4);
|
|
if (!entry.CheckPlacement (file.MaxOffset))
|
|
return null;
|
|
dir.Add (entry);
|
|
dir_offset += name_size+8;
|
|
}
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
|
{
|
|
if (!IsScriptFile (entry.Name))
|
|
return arc.File.CreateStream (entry.Offset, entry.Size);
|
|
var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
|
|
DecodeScript (data);
|
|
return new MemoryStream (data);
|
|
}
|
|
|
|
private static void DecodeScript (byte[] data)
|
|
{
|
|
for (int i = 0; i < data.Length; ++i)
|
|
{
|
|
byte v = data[i];
|
|
data[i] = (byte)(v >> 2 | v << 6);
|
|
}
|
|
}
|
|
|
|
private static void EncodeScript (byte[] data)
|
|
{
|
|
for (int i = 0; i < data.Length; ++i)
|
|
{
|
|
byte v = data[i];
|
|
data[i] = (byte)(v << 2 | v >> 6);
|
|
}
|
|
}
|
|
|
|
public override ResourceOptions GetDefaultOptions ()
|
|
{
|
|
return new ArcOptions { NameLength = Settings.Default.ARCNameLength };
|
|
}
|
|
|
|
public override object GetCreationWidget ()
|
|
{
|
|
return new GUI.CreateARCWidget();
|
|
}
|
|
|
|
internal class ArcEntry : Entry
|
|
{
|
|
public byte[] RawName;
|
|
}
|
|
|
|
internal class ArcDirectory
|
|
{
|
|
public byte[] Extension;
|
|
public uint DirOffset;
|
|
public List<ArcEntry> Files;
|
|
}
|
|
|
|
public override void Create (Stream output, IEnumerable<Entry> list, ResourceOptions options,
|
|
EntryCallback callback)
|
|
{
|
|
var arc_options = GetOptions<ArcOptions> (options);
|
|
var encoding = Encodings.cp932.WithFatalFallback();
|
|
|
|
int file_count = 0;
|
|
var file_table = new SortedDictionary<string, ArcDirectory>();
|
|
foreach (var entry in list)
|
|
{
|
|
string ext = Path.GetExtension (entry.Name).TrimStart ('.').ToUpperInvariant();
|
|
if (string.IsNullOrEmpty (ext))
|
|
throw new InvalidFileName (entry.Name, arcStrings.MsgNoExtension);
|
|
if (ext.Length > 3)
|
|
throw new InvalidFileName (entry.Name, arcStrings.MsgExtensionTooLong);
|
|
string name = Path.GetFileNameWithoutExtension (entry.Name).ToUpperInvariant();
|
|
byte[] raw_name = encoding.GetBytes (name);
|
|
if (raw_name.Length > arc_options.NameLength)
|
|
throw new InvalidFileName (entry.Name, arcStrings.MsgFileNameTooLong);
|
|
|
|
ArcDirectory dir;
|
|
if (!file_table.TryGetValue (ext, out dir))
|
|
{
|
|
byte[] raw_ext = encoding.GetBytes (ext);
|
|
if (raw_ext.Length > 3)
|
|
throw new InvalidFileName (entry.Name, arcStrings.MsgExtensionTooLong);
|
|
dir = new ArcDirectory { Extension = raw_ext, Files = new List<ArcEntry>() };
|
|
file_table[ext] = dir;
|
|
}
|
|
dir.Files.Add (new ArcEntry { Name = entry.Name, RawName = raw_name });
|
|
++file_count;
|
|
}
|
|
if (null != callback)
|
|
callback (file_count+1, null, null);
|
|
|
|
int callback_count = 0;
|
|
long dir_offset = 4 + file_table.Count * 12;
|
|
long data_offset = dir_offset + (arc_options.NameLength + 9) * file_count;
|
|
output.Position = data_offset;
|
|
foreach (var ext in file_table.Keys)
|
|
{
|
|
var dir = file_table[ext];
|
|
dir.DirOffset = (uint)dir_offset;
|
|
dir_offset += (arc_options.NameLength + 9) * dir.Files.Count;
|
|
foreach (var entry in dir.Files)
|
|
{
|
|
if (null != callback)
|
|
callback (callback_count++, entry, arcStrings.MsgAddingFile);
|
|
entry.Offset = data_offset;
|
|
entry.Size = WriteEntry (entry.Name, output);
|
|
data_offset += entry.Size;
|
|
if (data_offset > uint.MaxValue)
|
|
throw new FileSizeException();
|
|
}
|
|
}
|
|
if (null != callback)
|
|
callback (callback_count++, null, arcStrings.MsgWritingIndex);
|
|
|
|
output.Position = 0;
|
|
using (var header = new BinaryWriter (output, encoding, true))
|
|
{
|
|
byte[] buffer = new byte[arc_options.NameLength+1];
|
|
header.Write (file_table.Count);
|
|
foreach (var ext in file_table)
|
|
{
|
|
Buffer.BlockCopy (ext.Value.Extension, 0, buffer, 0, ext.Value.Extension.Length);
|
|
for (int i = ext.Value.Extension.Length; i < 4; ++i)
|
|
buffer[i] = 0;
|
|
header.Write (buffer, 0, 4);
|
|
header.Write (ext.Value.Files.Count);
|
|
header.Write (ext.Value.DirOffset);
|
|
}
|
|
foreach (var ext in file_table)
|
|
{
|
|
foreach (var entry in ext.Value.Files)
|
|
{
|
|
Buffer.BlockCopy (entry.RawName, 0, buffer, 0, entry.RawName.Length);
|
|
for (int i = entry.RawName.Length; i < buffer.Length; ++i)
|
|
buffer[i] = 0;
|
|
header.Write (buffer);
|
|
header.Write (entry.Size);
|
|
header.Write ((uint)entry.Offset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private uint WriteEntry (string filename, Stream output)
|
|
{
|
|
if (!IsScriptFile (filename))
|
|
{
|
|
using (var input = File.OpenRead (filename))
|
|
{
|
|
var size = input.Length;
|
|
if (size > uint.MaxValue)
|
|
throw new FileSizeException();
|
|
input.CopyTo (output);
|
|
return (uint)size;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var input = File.ReadAllBytes (filename);
|
|
EncodeScript (input);
|
|
output.Write (input, 0, input.Length);
|
|
return (uint)input.Length;
|
|
}
|
|
}
|
|
|
|
private static bool IsScriptFile (string filename)
|
|
{
|
|
return filename.EndsWith (".scr", StringComparison.InvariantCultureIgnoreCase)
|
|
|| filename.EndsWith (".wsc", StringComparison.InvariantCultureIgnoreCase);
|
|
}
|
|
}
|
|
}
|