diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj
index a6ef9dd4..e123709c 100644
--- a/ArcFormats/ArcFormats.csproj
+++ b/ArcFormats/ArcFormats.csproj
@@ -63,6 +63,9 @@
+
+ CreateSGWidget.xaml
+
CreateXP3Widget.xaml
@@ -119,6 +122,10 @@
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
diff --git a/ArcFormats/ArcSteinsGate.cs b/ArcFormats/ArcSteinsGate.cs
index 4dfd988f..ffb6b4e2 100644
--- a/ArcFormats/ArcSteinsGate.cs
+++ b/ArcFormats/ArcSteinsGate.cs
@@ -2,16 +2,43 @@
//! \date Thu Jul 24 23:36:01 2014
//! \brief Nitro+ Steins;Gate archive implementation.
//
+// Copyright (C) 2014 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.IO;
using System.Text;
+using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using GameRes.Formats.Strings;
using GameRes.Utility;
+using GameRes.Formats.Properties;
namespace GameRes.Formats
{
+ public class SteinsGateOptions : ResourceOptions
+ {
+ public Encoding FileNameEncoding { get; set; }
+ }
+
[Export(typeof(ArchiveFormat))]
public class NpaSteinsGateOpener : ArchiveFormat
{
@@ -19,6 +46,7 @@ namespace GameRes.Formats
public override string Description { get { return arcStrings.NPASteinsGateDescription; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return true; } }
+ public override bool CanCreate { get { return true; } }
public NpaSteinsGateOpener ()
{
@@ -37,22 +65,25 @@ namespace GameRes.Formats
return null;
var stream = new SteinsGateEncryptedStream (file, 4, (uint)index_size);
- using (var header = new BinaryReader (stream, Encoding.Unicode))
+ using (var header = new BinaryReader (stream))
{
int entry_count = header.ReadInt32();
if (entry_count <= 0)
return null;
+ index_size -= 4;
int average_entry_size = index_size / entry_count;
if (average_entry_size < 0x11)
return null;
- var dir = new List ((int)entry_count);
- for (uint i = 0; i < entry_count; ++i)
+ var dir = new List (entry_count);
+ for (int i = 0; i < entry_count; ++i)
{
int name_length = header.ReadInt32();
if (name_length+0x10 > index_size)
return null;
- string filename = new string (header.ReadChars (name_length/2));
+ byte[] name_raw = header.ReadBytes (name_length);
+ Encoding enc = GuessEncoding (name_raw);
+ string filename = enc.GetString (name_raw);
var entry = FormatCatalog.Instance.CreateEntry (filename);
entry.Size = header.ReadUInt32();
@@ -79,19 +110,179 @@ namespace GameRes.Formats
buffer[offset+i] ^= KeyString[i & 7];
}
}
+
+ Encoding GuessEncoding (byte[] text)
+ {
+ bool has_zero = false;
+ bool has_non_ascii = false;
+ foreach (var symbol in text)
+ {
+ if (0 == symbol)
+ {
+ has_zero = true;
+ break;
+ }
+ else if (symbol > 0x7f)
+ {
+ has_non_ascii = true;
+ }
+ }
+ if (has_zero)
+ return Encoding.Unicode;
+ else if (has_non_ascii)
+ return Encodings.cp932;
+ else
+ return Encoding.ASCII;
+ }
+
+ public override ResourceOptions GetDefaultOptions ()
+ {
+ return new SteinsGateOptions {
+ FileNameEncoding = GetEncoding (Settings.Default.SGFileNameEncoding),
+ };
+ }
+
+ public override object GetCreationWidget ()
+ {
+ return new GUI.CreateSGWidget();
+ }
+
+ Encoding GetEncoding (string name)
+ {
+ if ("shift-jis" == name)
+ return Encodings.cp932;
+ if ("utf-16" == name)
+ return Encoding.Unicode;
+ return Encoding.Default;
+ }
+
+ internal class RawEntry : Entry
+ {
+ public byte[] IndexName;
+ }
+
+ public override void Create (Stream output, IEnumerable list, ResourceOptions options,
+ EntryCallback callback)
+ {
+ var sg_options = GetOptions (options);
+ Encoding encoding = Encodings.WithFatalFallback (sg_options.FileNameEncoding);
+ long start_pos = output.Position;
+ int callback_count = 0;
+
+ uint index_size = 4;
+ int entry_count = list.Count();
+ var real_entry_list = new List (entry_count);
+ var used_names = new HashSet();
+ foreach (var entry in list)
+ {
+ string name = entry.Name.Replace (@"\", "/");
+ if (!used_names.Add (name)) // duplicate name
+ continue;
+ var header_entry = new RawEntry { Name = entry.Name };
+ try
+ {
+ header_entry.IndexName = encoding.GetBytes (name);
+ }
+ catch (EncoderFallbackException X)
+ {
+ throw new InvalidFileName (entry.Name, arcStrings.MsgIllegalCharacters, X);
+ }
+ index_size += (uint)header_entry.IndexName.Length + 16;
+ real_entry_list.Add (header_entry);
+ }
+ output.Seek (4+index_size, SeekOrigin.Current);
+ foreach (var entry in real_entry_list)
+ {
+ using (var input = File.Open (entry.Name, FileMode.Open, FileAccess.Read))
+ {
+ var file_size = input.Length;
+ if (file_size > uint.MaxValue)
+ throw new FileSizeException();
+ entry.Offset = output.Position;
+ entry.Size = (uint)file_size;
+ if (null != callback)
+ callback (callback_count++, entry, arcStrings.MsgAddingFile);
+ using (var stream = new SteinsGateEncryptedStream (output))
+ input.CopyTo (stream);
+ }
+ }
+ if (null != callback)
+ callback (callback_count++, null, arcStrings.MsgWritingIndex);
+ output.Position = start_pos;
+ output.WriteByte ((byte)(index_size & 0xff));
+ output.WriteByte ((byte)((index_size >> 8) & 0xff));
+ output.WriteByte ((byte)((index_size >> 16) & 0xff));
+ output.WriteByte ((byte)((index_size >> 24) & 0xff));
+ var encrypted_stream = new SteinsGateEncryptedStream (output);
+ using (var header = new BinaryWriter (encrypted_stream))
+ {
+ header.Write (entry_count);
+ foreach (var entry in real_entry_list)
+ {
+ header.Write (entry.IndexName.Length);
+ header.Write (entry.IndexName);
+ header.Write ((uint)entry.Size);
+ header.Write ((long)entry.Offset);
+ }
+ }
+ }
}
- public class SteinsGateEncryptedStream : ArcView.ArcStream
+ public class SteinsGateEncryptedStream : Stream
{
- public SteinsGateEncryptedStream (ArcView file, long offset, uint size)
- : base (file, offset, size)
+ private Stream m_stream;
+ private long m_base_pos;
+ private bool m_should_dispose;
+
+ public Stream BaseStream { get { return m_stream; } }
+
+ public override bool CanRead { get { return m_stream.CanRead; } }
+ public override bool CanSeek { get { return m_stream.CanSeek; } }
+ public override bool CanWrite { get { return m_stream.CanWrite; } }
+ public override long Length { get { return m_stream.Length - m_base_pos; } }
+ public override long Position
{
+ get { return m_stream.Position - m_base_pos; }
+ set { m_stream.Position = m_base_pos + value; }
+ }
+
+ public SteinsGateEncryptedStream (ArcView file, long offset, uint size)
+ {
+ m_stream = file.CreateStream (offset, size);
+ m_should_dispose = true;
+ m_base_pos = 0;
+ }
+
+ public SteinsGateEncryptedStream (Stream output)
+ {
+ m_stream = output;
+ m_should_dispose = false;
+ m_base_pos = m_stream.Position;
+ }
+
+ #region System.IO.Stream methods
+ public override void Flush()
+ {
+ m_stream.Flush();
+ }
+
+ public override long Seek (long offset, SeekOrigin origin)
+ {
+ if (SeekOrigin.Begin == origin)
+ offset += m_base_pos;
+ offset = m_stream.Seek (offset, origin);
+ return offset - m_base_pos;
+ }
+
+ public override void SetLength (long length)
+ {
+ throw new System.NotSupportedException ("SteinsGateEncryptedStream.SetLength method is not supported");
}
public override int Read (byte[] buffer, int offset, int count)
{
int position = (int)Position & 7;
- int read = base.Read (buffer, offset, count);
+ int read = m_stream.Read (buffer, offset, count);
if (read > 0)
{
for (int i = 0; i < read; ++i)
@@ -105,12 +296,46 @@ namespace GameRes.Formats
public override int ReadByte ()
{
int position = (int)Position & 7;
- int b = base.ReadByte();
+ int b = m_stream.ReadByte();
if (-1 != b)
{
b ^= NpaSteinsGateOpener.KeyString[position];
}
return b;
}
+
+ public override void Write (byte[] buffer, int offset, int count)
+ {
+ int position = (int)Position & 7;
+ byte[] write_buf = new byte[count];
+ for (int i = 0; i < count; ++i)
+ {
+ write_buf[i] = (byte)(buffer[offset+i] ^ NpaSteinsGateOpener.KeyString[(position+i)&7]);
+ }
+ m_stream.Write (write_buf, 0, count);
+ }
+
+ public override void WriteByte (byte value)
+ {
+ int position = (int)Position & 7;
+ m_stream.WriteByte ((byte)(value ^ NpaSteinsGateOpener.KeyString[position]));
+ }
+ #endregion
+
+ #region IDisposable Members
+ bool disposed = false;
+ protected override void Dispose (bool disposing)
+ {
+ if (!disposed)
+ {
+ if (disposing && m_should_dispose)
+ {
+ m_stream.Dispose();
+ }
+ disposed = true;
+ base.Dispose (disposing);
+ }
+ }
+ #endregion
}
}
diff --git a/ArcFormats/CreateSGWidget.xaml b/ArcFormats/CreateSGWidget.xaml
new file mode 100644
index 00000000..6ceaaa75
--- /dev/null
+++ b/ArcFormats/CreateSGWidget.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ArcFormats/CreateSGWidget.xaml.cs b/ArcFormats/CreateSGWidget.xaml.cs
new file mode 100644
index 00000000..e78df245
--- /dev/null
+++ b/ArcFormats/CreateSGWidget.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace GameRes.Formats.GUI
+{
+ ///
+ /// Interaction logic for CreateSGWidget.xaml
+ ///
+ public partial class CreateSGWidget : Grid
+ {
+ public CreateSGWidget ()
+ {
+ InitializeComponent ();
+ }
+ }
+}
diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs
index 914db947..a9445ed2 100644
--- a/ArcFormats/Properties/Settings.Designer.cs
+++ b/ArcFormats/Properties/Settings.Designer.cs
@@ -117,5 +117,17 @@ namespace GameRes.Formats.Properties {
this["XP3RetainStructure"] = value;
}
}
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("shift-jis")]
+ public string SGFileNameEncoding {
+ get {
+ return ((string)(this["SGFileNameEncoding"]));
+ }
+ set {
+ this["SGFileNameEncoding"] = value;
+ }
+ }
}
}
diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings
index 1df61621..ed32b483 100644
--- a/ArcFormats/Properties/Settings.settings
+++ b/ArcFormats/Properties/Settings.settings
@@ -26,5 +26,8 @@
False
+
+ shift-jis
+
\ No newline at end of file
diff --git a/ArcFormats/Strings/arcStrings.Designer.cs b/ArcFormats/Strings/arcStrings.Designer.cs
index e3693097..03932d18 100644
--- a/ArcFormats/Strings/arcStrings.Designer.cs
+++ b/ArcFormats/Strings/arcStrings.Designer.cs
@@ -261,6 +261,15 @@ namespace GameRes.Formats.Strings {
}
}
+ ///
+ /// Looks up a localized string similar to Filename encoding.
+ ///
+ public static string SGLabelEncoding {
+ get {
+ return ResourceManager.GetString("SGLabelEncoding", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Liar-soft game resource archive.
///
diff --git a/ArcFormats/Strings/arcStrings.resx b/ArcFormats/Strings/arcStrings.resx
index 65feab3e..2c752c56 100644
--- a/ArcFormats/Strings/arcStrings.resx
+++ b/ArcFormats/Strings/arcStrings.resx
@@ -186,6 +186,9 @@ predefined encryption scheme.
Amaterasu Translations Muv-Luv script file
+
+ Filename encoding
+
Liar-soft game resource archive
diff --git a/ArcFormats/Strings/arcStrings.ru-RU.resx b/ArcFormats/Strings/arcStrings.ru-RU.resx
index ec5f4ac0..9b1a589e 100644
--- a/ArcFormats/Strings/arcStrings.ru-RU.resx
+++ b/ArcFormats/Strings/arcStrings.ru-RU.resx
@@ -159,6 +159,9 @@
Записывается оглавление...
+
+ Кодировка имён файлов
+
Сжать содержимое
diff --git a/ArcFormats/app.config b/ArcFormats/app.config
index a447bb45..7f2bf1c5 100644
--- a/ArcFormats/app.config
+++ b/ArcFormats/app.config
@@ -28,6 +28,9 @@
False
+
+ shift-jis
+
\ No newline at end of file