mirror of
https://github.com/ficed/Braver.git
synced 2024-11-26 22:50:38 +00:00
Alter music to read via normal data sources so plugins/mods can override it
Config utility copes with name/description attributes, nested/category options .IRO support for 7H plugin Slightly more concise Tolk output
This commit is contained in:
parent
9bb83d967d
commit
7d6611e04a
@ -3,3 +3,4 @@ syntax: glob
|
||||
*/obj/*
|
||||
*/bin/*
|
||||
*.user
|
||||
PluginImplementations/output/*
|
||||
|
@ -7,6 +7,7 @@
|
||||
using Ficedula.FF7;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
@ -48,6 +49,8 @@ namespace Braver {
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string ToString() => $"Pack";
|
||||
}
|
||||
|
||||
public class LGPDataSource : DataSource {
|
||||
@ -59,6 +62,8 @@ namespace Braver {
|
||||
|
||||
public override IEnumerable<string> Scan() => _lgp.Filenames;
|
||||
public override Stream TryOpen(string file) => _lgp.TryOpen(file);
|
||||
|
||||
public override string ToString() => _lgp.ToString();
|
||||
}
|
||||
|
||||
public class FileDataSource : DataSource {
|
||||
@ -79,6 +84,8 @@ namespace Braver {
|
||||
return new FileStream(fn, FileMode.Open, FileAccess.Read);
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string ToString() => $"File source {_root}";
|
||||
}
|
||||
|
||||
public class GameOptions {
|
||||
@ -150,6 +157,7 @@ namespace Braver {
|
||||
public void AddDataSource(string folder, DataSource source) {
|
||||
if (!_data.TryGetValue(folder, out var list))
|
||||
list = _data[folder] = new List<DataSource>();
|
||||
Trace.WriteLine($"Adding data source for folder {folder}: {source}");
|
||||
list.Add(source);
|
||||
}
|
||||
public string GetPath(string name) => _paths[name];
|
||||
|
29
Braver.Plugins/Attributes.cs
Normal file
29
Braver.Plugins/Attributes.cs
Normal file
@ -0,0 +1,29 @@
|
||||
// This program and the accompanying materials are made available under the terms of the
|
||||
// Eclipse Public License v2.0 which accompanies this distribution, and is available at
|
||||
// https://www.eclipse.org/legal/epl-v20.html
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Braver.Plugins {
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ConfigPropertyAttribute : Attribute {
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public ConfigPropertyAttribute(string name) {
|
||||
Name = name;
|
||||
}
|
||||
public ConfigPropertyAttribute(string name, string description) {
|
||||
Name = name;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
10
Braver.sln
10
Braver.sln
@ -28,9 +28,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Braver.Tolk", "PluginImplem
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BraverLauncher", "BraverLauncher\BraverLauncher.csproj", "{504E8DFB-B109-4A22-88E8-55E728903E2A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Braver.7HShim", "PluginImplementations\Braver.7HShim\Braver.7HShim.csproj", "{4A5088CD-48E8-494B-A82C-8212A5D49073}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Braver.7HShim", "PluginImplementations\Braver.7HShim\Braver.7HShim.csproj", "{4A5088CD-48E8-494B-A82C-8212A5D49073}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Braver.FFNxCompatibility", "PluginImplementations\Braver.FFNxCompatibility\Braver.FFNxCompatibility.csproj", "{0C0F23F0-D790-4102-BBD4-56774E4A089B}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Braver.FFNxCompatibility", "PluginImplementations\Braver.FFNxCompatibility\Braver.FFNxCompatibility.csproj", "{0C0F23F0-D790-4102-BBD4-56774E4A089B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IrosArchive", "IrosArchive\IrosArchive.csproj", "{803429DC-A6B4-43C2-9F73-D6EA51EA6B99}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -86,6 +88,10 @@ Global
|
||||
{0C0F23F0-D790-4102-BBD4-56774E4A089B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0C0F23F0-D790-4102-BBD4-56774E4A089B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0C0F23F0-D790-4102-BBD4-56774E4A089B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{803429DC-A6B4-43C2-9F73-D6EA51EA6B99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{803429DC-A6B4-43C2-9F73-D6EA51EA6B99}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{803429DC-A6B4-43C2-9F73-D6EA51EA6B99}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{803429DC-A6B4-43C2-9F73-D6EA51EA6B99}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -19,16 +19,14 @@ namespace Braver {
|
||||
|
||||
public class Audio : IAudio {
|
||||
|
||||
private string _musicFolder;
|
||||
private Channel<MusicCommand> _channel;
|
||||
private Ficedula.FF7.Audio _sfxSource;
|
||||
private FGame _game;
|
||||
private byte _volume = 127;
|
||||
private float _masterVolume;
|
||||
|
||||
public Audio(FGame game, string musicFolder, string soundFolder) {
|
||||
public Audio(FGame game, string soundFolder) {
|
||||
_game = game;
|
||||
_musicFolder = musicFolder;
|
||||
_masterVolume = game.GameOptions.MusicVolume;
|
||||
_channel = Channel.CreateBounded<MusicCommand>(8);
|
||||
Task.Run(RunMusic);
|
||||
@ -153,15 +151,15 @@ namespace Braver {
|
||||
void DoPlay(string track) {
|
||||
var current = contexts.Peek();
|
||||
DoStop();
|
||||
string file = Path.Combine(_musicFolder, track + ".ogg");
|
||||
if (!File.Exists(file)) {
|
||||
System.Diagnostics.Trace.WriteLine($"Failed to find music track {file}");
|
||||
var source = _game.TryOpen("vgmstream", track + ".ogg");
|
||||
if (source == null) {
|
||||
Trace.WriteLine($"Failed to find music track {track}.ogg");
|
||||
return;
|
||||
}
|
||||
|
||||
int loopStart = 0, loopEnd = 0;
|
||||
try {
|
||||
using (var reader = new NVorbis.VorbisReader(file)) {
|
||||
using (var reader = new NVorbis.VorbisReader(source, false)) {
|
||||
foreach(var tag in reader.Tags.All)
|
||||
Trace.WriteLine($"Vorbis tag: {tag.Key} = {string.Join(", ", tag.Value)}");
|
||||
loopStart = int.Parse(reader.Tags.GetTagSingle("LOOPSTART"));
|
||||
@ -170,7 +168,8 @@ namespace Braver {
|
||||
} catch (Exception ex) {
|
||||
Trace.WriteLine($"Failed to parse vorbis tags: {ex}");
|
||||
}
|
||||
current.Vorbis = new NAudio.Vorbis.VorbisWaveReader(file);
|
||||
source.Position = 0;
|
||||
current.Vorbis = new NAudio.Vorbis.VorbisWaveReader(source, true);
|
||||
current.WaveOut = new NAudio.Wave.WaveOut();
|
||||
//current.WaveOut.Init(current.Vorbis);
|
||||
current.Volume = new VolumeSampleProvider(new LoopProvider(current.Vorbis, loopStart, loopEnd));
|
||||
|
@ -122,7 +122,7 @@ namespace Braver {
|
||||
throw new NotSupportedException($"Unrecognised data spec {spec}");
|
||||
}
|
||||
|
||||
Audio = new Audio(this, _paths["MUSIC"], _paths["SFX"]);
|
||||
Audio = new Audio(this, _paths["SFX"]);
|
||||
|
||||
Audio.Precache(Sfx.Cursor, true);
|
||||
Audio.Precache(Sfx.Cancel, true);
|
||||
|
@ -36,7 +36,7 @@ DATA PACK? save %bdata%
|
||||
DATA PACK? ui %bdata%
|
||||
DATA PACK? wm %bdata%
|
||||
|
||||
PATH MUSIC %music%
|
||||
DATA FILE? vgmstream %music%
|
||||
PATH SFX %ff7%\data\sound
|
||||
PATH FFMPEG %braver%\ffmpeg.exe
|
||||
PATH MOVIES %movies%
|
||||
|
@ -66,12 +66,14 @@
|
||||
<ColumnDefinition Width="2*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ListBox x:Name="lbPlugins" SelectionChanged="lbPlugins_SelectionChanged" />
|
||||
<Grid x:Name="gPluginConfig" Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Column="1">
|
||||
<Grid x:Name="gPluginConfig" Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
@ -14,6 +14,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
@ -132,10 +133,12 @@ namespace BraverLauncher {
|
||||
void DoAdd(string name, Control c) {
|
||||
gPluginConfig.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
|
||||
if (!string.IsNullOrEmpty(name)) {
|
||||
var lbl = new Label { Content = name };
|
||||
var lbl = new Label { Content = name, Margin = new Thickness(3) };
|
||||
Grid.SetRow(lbl, gPluginConfig.RowDefinitions.Count - 1);
|
||||
gPluginConfig.Children.Add(lbl);
|
||||
AutomationProperties.SetLabeledBy(c, lbl);
|
||||
}
|
||||
c.Margin = new Thickness(3);
|
||||
Grid.SetRow(c, gPluginConfig.RowDefinitions.Count - 1);
|
||||
Grid.SetColumn(c, 1);
|
||||
gPluginConfig.Children.Add(c);
|
||||
@ -157,24 +160,64 @@ namespace BraverLauncher {
|
||||
DoAdd("", enabled);
|
||||
enabled.Checked += (_o, _e) => config.Enabled = enabled.IsChecked ?? false;
|
||||
|
||||
foreach (var prop in plugin.Plugin.ConfigObject.GetType().GetProperties()) {
|
||||
var v = config.Vars.FirstOrDefault(cv => cv.Name == prop.Name);
|
||||
if (prop.PropertyType == typeof(string)) {
|
||||
TextBox tb = new TextBox {
|
||||
Text = v.Value ?? prop.GetValue(plugin.Plugin.ConfigObject)?.ToString() ?? string.Empty,
|
||||
void DoObj(object o, IEnumerable<string> path, string category) {
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(category)) {
|
||||
gPluginConfig.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
|
||||
var heading = new TextBlock {
|
||||
Text = category,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 5, 0, 0),
|
||||
};
|
||||
DoAdd(prop.Name, tb);
|
||||
tb.TextChanged += (_o, _e) => SetProp(prop.Name, tb.Text);
|
||||
} else if (prop.PropertyType == typeof(bool)) {
|
||||
CheckBox cb = new CheckBox {
|
||||
Content = prop.Name,
|
||||
IsChecked = (bool)prop.GetValue(plugin.Plugin.ConfigObject),
|
||||
};
|
||||
DoAdd("", cb);
|
||||
cb.Checked += (_o, _e) => SetProp(prop.Name, (cb.IsChecked ?? false).ToString());
|
||||
} else
|
||||
throw new NotImplementedException();
|
||||
Grid.SetColumnSpan(heading, 2);
|
||||
Grid.SetRow(heading, gPluginConfig.RowDefinitions.Count - 1);
|
||||
gPluginConfig.Children.Add(heading);
|
||||
}
|
||||
|
||||
foreach (var prop in o.GetType().GetProperties()) {
|
||||
string fullPropName = string.Join(".", path.Concat(new[] { prop.Name }));
|
||||
var v = config.Vars.FirstOrDefault(cv => cv.Name == fullPropName);
|
||||
var attr = prop.GetCustomAttributes(true).OfType<ConfigPropertyAttribute>().FirstOrDefault();
|
||||
string name = attr?.Name ?? prop.Name,
|
||||
desc = attr?.Description;
|
||||
|
||||
if (prop.PropertyType == typeof(string)) {
|
||||
TextBox tb = new TextBox {
|
||||
Text = v.Value ?? prop.GetValue(o)?.ToString() ?? string.Empty,
|
||||
};
|
||||
DoAdd(name, tb);
|
||||
tb.TextChanged += (_o, _e) => SetProp(fullPropName, tb.Text);
|
||||
} else if (prop.PropertyType == typeof(bool)) {
|
||||
CheckBox cb = new CheckBox {
|
||||
Content = name,
|
||||
IsChecked = v == null ? (bool)prop.GetValue(o) : bool.Parse(v.Value),
|
||||
};
|
||||
DoAdd("", cb);
|
||||
cb.Checked += (_o, _e) => SetProp(fullPropName, (cb.IsChecked ?? false).ToString());
|
||||
} else if (prop.PropertyType.IsEnum) {
|
||||
object val;
|
||||
if (string.IsNullOrEmpty(v?.Value))
|
||||
val = prop.GetValue(o);
|
||||
else
|
||||
val = Enum.Parse(prop.PropertyType, v?.Value);
|
||||
ComboBox cb = new ComboBox {
|
||||
ItemsSource = Enum.GetValues(prop.PropertyType),
|
||||
SelectedValue = val,
|
||||
};
|
||||
DoAdd(name, cb);
|
||||
cb.SelectionChanged += (_o, _e) => SetProp(fullPropName, cb.SelectedValue.ToString());
|
||||
} else if (!prop.PropertyType.IsValueType) {
|
||||
DoObj(
|
||||
prop.GetValue(o),
|
||||
path.Concat(new[] { prop.Name }),
|
||||
(category + " " + name).Trim()
|
||||
);
|
||||
} else
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
DoObj(plugin.Plugin.ConfigObject, Enumerable.Empty<string>(), "");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"BraverLauncher": {
|
||||
"commandName": "Project"
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "/plugins:C:\\Users\\ficed\\Projects\\F7\\PluginImplementations\\output\\Debug"
|
||||
}
|
||||
}
|
||||
}
|
@ -97,5 +97,7 @@ namespace Ficedula.FF7 {
|
||||
public void Dispose() {
|
||||
_source.Dispose();
|
||||
}
|
||||
|
||||
public override string ToString() => $"LGP {_source}";
|
||||
}
|
||||
}
|
||||
|
385
IrosArchive/IrosArc.cs
Normal file
385
IrosArchive/IrosArc.cs
Normal file
@ -0,0 +1,385 @@
|
||||
/*
|
||||
This source is subject to the Microsoft Public License. See LICENSE.TXT for details.
|
||||
The original developer is Iros <irosff@outlook.com>
|
||||
|
||||
Modified by Ficedula to split out into a separate library not depending on anything else in 7H,
|
||||
for read only access to IRO archives.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace IrosArchive {
|
||||
|
||||
public static class StreamUtil {
|
||||
public static long ReadLong(this Stream s) {
|
||||
byte[] b = new byte[8];
|
||||
s.Read(b, 0, 8);
|
||||
return BitConverter.ToInt64(b);
|
||||
}
|
||||
public static int ReadInt(this Stream s) {
|
||||
byte[] b = new byte[4];
|
||||
s.Read(b, 0, 4);
|
||||
return BitConverter.ToInt32(b);
|
||||
}
|
||||
public static ushort ReadUShort(this Stream s) {
|
||||
byte[] b = new byte[2];
|
||||
s.Read(b, 0, 2);
|
||||
return BitConverter.ToUInt16(b);
|
||||
}
|
||||
}
|
||||
|
||||
public class IrosArcException : Exception {
|
||||
public IrosArcException(string msg) : base(msg) { }
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ArchiveFlags {
|
||||
None = 0,
|
||||
Patch = 0x1,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FileFlags {
|
||||
None = 0,
|
||||
CompressLZS = 0x1,
|
||||
CompressLZMA = 0x2,
|
||||
|
||||
COMPRESSION_FLAGS = 0xF,
|
||||
|
||||
#if RUDE
|
||||
Obfuscate = 0x10000,
|
||||
#else
|
||||
RudeFlags = 0xff0000,
|
||||
#endif
|
||||
}
|
||||
|
||||
public enum CompressType {
|
||||
Nothing = 0,
|
||||
Everything,
|
||||
ByExtension,
|
||||
ByContent,
|
||||
}
|
||||
|
||||
public class IrosArc : IDisposable {
|
||||
public const int SIG = 0x534f5249;
|
||||
public const int MAX_VERSION = 0x10002;
|
||||
public const int MIN_VERSION = 0x10000;
|
||||
|
||||
internal const int SZ_OK = 0;
|
||||
internal const int SZ_ERROR_DATA = 1;
|
||||
internal const int SZ_ERROR_MEM = 2;
|
||||
internal const int SZ_ERROR_CRC = 3;
|
||||
internal const int SZ_ERROR_UNSUPPORTED = 4;
|
||||
internal const int SZ_ERROR_PARAM = 5;
|
||||
internal const int SZ_ERROR_INPUT_EOF = 6;
|
||||
internal const int SZ_ERROR_OUTPUT_EOF = 7;
|
||||
|
||||
private static HashSet<string> _noCompressExt = new HashSet<string>(new[] {
|
||||
".jpg", ".png", ".mp3", ".ogg"
|
||||
}, StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
private class ArcHeader {
|
||||
|
||||
public int Version { get; set; }
|
||||
public ArchiveFlags Flags { get; set; }
|
||||
public int Directory { get; set; }
|
||||
|
||||
public void Open(Stream s) {
|
||||
if (s.ReadInt() != SIG) throw new IrosArcException("Signature mismatch");
|
||||
Version = s.ReadInt();
|
||||
Flags = (ArchiveFlags)s.ReadInt();
|
||||
Directory = s.ReadInt();
|
||||
if (Version < MIN_VERSION) throw new IrosArcException("Invalid header version " + Version.ToString());
|
||||
if (Version > MAX_VERSION) throw new IrosArcException("Invalid header version " + Version.ToString());
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return String.Format("Version: {0}.{1} Directory at: {2} Flags: {3}", Version >> 16, Version & 0xffff, Directory, Flags);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class DirectoryEntry {
|
||||
public string Filename { get; set; }
|
||||
public FileFlags Flags { get; set; }
|
||||
public long Offset { get; set; }
|
||||
public int Length { get; set; }
|
||||
|
||||
public void Open(Stream s, int version) {
|
||||
long pos = s.Position;
|
||||
ushort len = s.ReadUShort();
|
||||
ushort flen = s.ReadUShort();
|
||||
byte[] fn = new byte[flen];
|
||||
s.Read(fn, 0, flen);
|
||||
Filename = System.Text.Encoding.Unicode.GetString(fn);
|
||||
Flags = (FileFlags)s.ReadInt();
|
||||
if (version < 0x10001)
|
||||
Offset = s.ReadInt();
|
||||
else
|
||||
Offset = s.ReadLong();
|
||||
Length = s.ReadInt();
|
||||
s.Position = pos + len;
|
||||
}
|
||||
|
||||
public ushort GetSize() {
|
||||
byte[] fndata = System.Text.Encoding.Unicode.GetBytes(Filename);
|
||||
ushort len = (ushort)(fndata.Length + 4 + 16);
|
||||
return len;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return String.Format("File: {0} Offset: {1} Size: {2} Flags: {3}", Filename, Offset, Length, Flags);
|
||||
}
|
||||
}
|
||||
|
||||
private ArcHeader _header;
|
||||
private List<DirectoryEntry> _entries;
|
||||
private Dictionary<string, DirectoryEntry> _lookup;
|
||||
private HashSet<string> _folderNames;
|
||||
private FileStream _data;
|
||||
private string _source;
|
||||
|
||||
private class CacheEntry {
|
||||
public byte[] Data;
|
||||
public DateTime LastAccess;
|
||||
public string File;
|
||||
}
|
||||
|
||||
private System.Collections.Concurrent.ConcurrentDictionary<long, CacheEntry> _cache = new System.Collections.Concurrent.ConcurrentDictionary<long, CacheEntry>();
|
||||
|
||||
private struct DataRecord {
|
||||
public byte[] Data;
|
||||
public bool Compressed;
|
||||
}
|
||||
private static DataRecord GetData(byte[] input, string filename, CompressType compress) {
|
||||
if (compress == CompressType.Nothing) {
|
||||
return new DataRecord() { Data = input };
|
||||
}
|
||||
if (compress == CompressType.ByExtension && _noCompressExt.Contains(Path.GetExtension(filename))) {
|
||||
return new DataRecord() { Data = input };
|
||||
}
|
||||
|
||||
var cdata = new MemoryStream();
|
||||
//Lzs.Encode(new MemoryStream(input), cdata);
|
||||
byte[] lprops;
|
||||
using (var lzma = new SharpCompress.Compressors.LZMA.LzmaStream(new SharpCompress.Compressors.LZMA.LzmaEncoderProperties(), false, cdata)) {
|
||||
lzma.Write(input, 0, input.Length);
|
||||
lprops = lzma.Properties;
|
||||
}
|
||||
if (/*compress == CompressType.ByContent &&*/ (cdata.Length + lprops.Length + 8) > (input.Length * 10 / 8)) {
|
||||
return new DataRecord() { Data = input };
|
||||
}
|
||||
|
||||
byte[] data = new byte[cdata.Length + lprops.Length + 8];
|
||||
Array.Copy(BitConverter.GetBytes(input.Length), data, 4);
|
||||
Array.Copy(BitConverter.GetBytes(lprops.Length), 0, data, 4, 4);
|
||||
Array.Copy(lprops, 0, data, 8, lprops.Length);
|
||||
cdata.Position = 0;
|
||||
cdata.Read(data, lprops.Length + 8, (int)cdata.Length);
|
||||
return new DataRecord() { Data = data, Compressed = true };
|
||||
}
|
||||
|
||||
public bool CheckValid() {
|
||||
foreach (var entry in _entries) {
|
||||
if ((entry.Offset + entry.Length) > _data.Length) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public IrosArc(string filename, bool patchable = false, Action<int, int> progressAction = null) {
|
||||
_source = filename;
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
if (patchable)
|
||||
_data = new FileStream(filename, FileMode.Open, FileAccess.ReadWrite);
|
||||
else
|
||||
_data = new FileStream(filename, FileMode.Open, FileAccess.Read);
|
||||
_header = new ArcHeader();
|
||||
_header.Open(_data);
|
||||
|
||||
int numfiles;
|
||||
_data.Position = _header.Directory;
|
||||
do {
|
||||
numfiles = _data.ReadInt();
|
||||
if (numfiles == -1) {
|
||||
_data.Position = _data.ReadLong();
|
||||
}
|
||||
} while (numfiles < 0);
|
||||
_entries = new List<DirectoryEntry>();
|
||||
_lookup = new Dictionary<string, DirectoryEntry>(StringComparer.InvariantCultureIgnoreCase);
|
||||
_folderNames = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
for (int i = 0; i < numfiles; i++) {
|
||||
progressAction?.Invoke(i, numfiles);
|
||||
DirectoryEntry e = new DirectoryEntry();
|
||||
e.Open(_data, _header.Version);
|
||||
#if !RUDE
|
||||
if ((e.Flags & FileFlags.RudeFlags) != 0) throw new IrosArcException(String.Format("Archive {0} entry {1} has invalid flags", filename, e.Filename));
|
||||
#endif
|
||||
|
||||
_entries.Add(e);
|
||||
_lookup[e.Filename] = e;
|
||||
int lpos = e.Filename.LastIndexOf('\\');
|
||||
if (lpos > 0) {
|
||||
_folderNames.Add(e.Filename.Substring(0, lpos));
|
||||
}
|
||||
}
|
||||
sw.Stop();
|
||||
Trace.WriteLine($"IrosArc: opened {filename}, contains {_lookup.Count} files, took {sw.ElapsedMilliseconds} ms to parse");
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames() {
|
||||
return _lookup.Keys;
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFolderNames() {
|
||||
return _folderNames.Select(s => s);
|
||||
}
|
||||
|
||||
public bool HasFolder(string name) {
|
||||
bool result = _folderNames.Contains(name);
|
||||
if (result) {
|
||||
Trace.WriteLine($"ARCHIVE: {_source} contains folder {name}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool HasFile(string name) {
|
||||
bool result = _lookup.ContainsKey(name);
|
||||
if (result) {
|
||||
Trace.WriteLine($"ARCHIVE: {_source} contains file {name}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public int GetFileSize(string name) {
|
||||
DirectoryEntry e;
|
||||
if (_lookup.TryGetValue(name, out e)) {
|
||||
switch (e.Flags & FileFlags.COMPRESSION_FLAGS) {
|
||||
case FileFlags.CompressLZMA:
|
||||
_data.Position = e.Offset;
|
||||
return _data.ReadInt();
|
||||
default:
|
||||
case FileFlags.None:
|
||||
return e.Length;
|
||||
}
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void CleanCache() {
|
||||
long[] remove = _cache
|
||||
.ToArray()
|
||||
.Where(kv => kv.Value.LastAccess < DateTime.Now.AddSeconds(-60))
|
||||
.Select(kv => kv.Key)
|
||||
.ToArray();
|
||||
if (remove.Any()) {
|
||||
Trace.WriteLine($"Removing {remove.Length} compressed files from cache: ");
|
||||
CacheEntry _;
|
||||
foreach (long r in remove) _cache.TryRemove(r, out _);
|
||||
}
|
||||
}
|
||||
|
||||
//private int _cacheCounter = 0;
|
||||
|
||||
private CacheEntry GetCache(DirectoryEntry e) {
|
||||
CacheEntry ce;
|
||||
if (!_cache.TryGetValue(e.Offset, out ce)) {
|
||||
ce = new CacheEntry() { File = e.Filename };
|
||||
byte[] data;
|
||||
lock (_data) {
|
||||
switch (e.Flags & FileFlags.COMPRESSION_FLAGS) {
|
||||
case FileFlags.CompressLZS:
|
||||
data = new byte[e.Length];
|
||||
_data.Position = e.Offset;
|
||||
_data.Read(data, 0, e.Length);
|
||||
var ms = new MemoryStream(data);
|
||||
var output = new MemoryStream();
|
||||
Lzs.Decode(ms, output);
|
||||
data = new byte[output.Length];
|
||||
output.Position = 0;
|
||||
output.Read(data, 0, data.Length);
|
||||
ce.Data = data;
|
||||
break;
|
||||
case FileFlags.CompressLZMA:
|
||||
_data.Position = e.Offset;
|
||||
int decSize = _data.ReadInt(), propSize = _data.ReadInt();
|
||||
byte[] props = new byte[propSize];
|
||||
_data.Read(props, 0, props.Length);
|
||||
byte[] cdata = new byte[e.Length - propSize - 8];
|
||||
_data.Read(cdata, 0, cdata.Length);
|
||||
data = new byte[decSize];
|
||||
var lzma = new SharpCompress.Compressors.LZMA.LzmaStream(props, new MemoryStream(cdata));
|
||||
lzma.Read(data, 0, data.Length);
|
||||
/*int srcSize = cdata.Length;
|
||||
switch (LzmaUncompress(data, ref decSize, cdata, ref srcSize, props, props.Length)) {
|
||||
case SZ_OK:
|
||||
//Woohoo!
|
||||
break;
|
||||
default:
|
||||
throw new IrosArcException("Error decompressing " + e.Filename);
|
||||
}*/
|
||||
ce.Data = data;
|
||||
break;
|
||||
default:
|
||||
throw new IrosArcException("Bad compression flags " + e.Flags.ToString());
|
||||
}
|
||||
}
|
||||
_cache.AddOrUpdate(e.Offset, ce, (_, __) => ce);
|
||||
}
|
||||
ce.LastAccess = DateTime.Now;
|
||||
CleanCache();
|
||||
|
||||
/*
|
||||
if ((_cacheCounter++ % 100) == 0)
|
||||
DebugLogger.WriteLine("IRO cache contents; " + String.Join(",", _cache.Values.Select(e => e.File)));
|
||||
*/
|
||||
|
||||
return ce;
|
||||
}
|
||||
|
||||
public byte[] GetBytes(string name) {
|
||||
DirectoryEntry e;
|
||||
if (_lookup.TryGetValue(name, out e)) {
|
||||
if ((e.Flags & FileFlags.COMPRESSION_FLAGS) != 0)
|
||||
return GetCache(e).Data;
|
||||
else {
|
||||
lock (_data) {
|
||||
byte[] data = new byte[e.Length];
|
||||
_data.Position = e.Offset;
|
||||
_data.Read(data, 0, e.Length);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
public Stream GetData(string name) {
|
||||
byte[] data = GetBytes(name);
|
||||
return data == null ? null : new MemoryStream(data);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (_data != null) {
|
||||
_data.Close();
|
||||
_data = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return "[IrosArchive " + _source + "]";
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetInformation() {
|
||||
yield return _header.ToString();
|
||||
|
||||
foreach (var entry in _entries)
|
||||
yield return entry.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
13
IrosArchive/IrosArchive.csproj
Normal file
13
IrosArchive/IrosArchive.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SharpCompress" Version="0.33.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
23
IrosArchive/LICENSE.txt
Normal file
23
IrosArchive/LICENSE.txt
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
Microsoft Public License (MS-PL)
|
||||
|
||||
This license governs use of the accompanying software. If you use the software, you
|
||||
accept this license. If you do not accept the license, do not use the software.
|
||||
|
||||
1. Definitions
|
||||
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
|
||||
same meaning here as under U.S. copyright law.
|
||||
A "contribution" is the original software, or any additions or changes to the software.
|
||||
A "contributor" is any person that distributes its contribution under this license.
|
||||
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||
|
||||
2. Grant of Rights
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
|
||||
|
||||
3. Conditions and Limitations
|
||||
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
|
||||
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
|
||||
(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
|
||||
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
|
197
IrosArchive/Lzs.cs
Normal file
197
IrosArchive/Lzs.cs
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
This source is subject to the Microsoft Public License. See LICENSE.TXT for details.
|
||||
The original developer is Iros <irosff@outlook.com>
|
||||
*/
|
||||
|
||||
namespace IrosArchive {
|
||||
public static class Lzs {
|
||||
private const int N = 4096;
|
||||
private const int F = 18;
|
||||
private const int THRESHOLD = 2;
|
||||
private const int NIL = N;
|
||||
|
||||
public static void Encode(Stream input, Stream output) {
|
||||
new EncodeContext().Encode(input, output);
|
||||
}
|
||||
public static void Decode(Stream input, Stream output) {
|
||||
new EncodeContext().Decode(input, output);
|
||||
}
|
||||
|
||||
private class EncodeContext {
|
||||
public byte[] buffer = new byte[N + F];
|
||||
public int MatchPos, MatchLen;
|
||||
public int[] Lson = new int[N + 1];
|
||||
public int[] Rson = new int[N + 257];
|
||||
public int[] Dad = new int[N + 1];
|
||||
|
||||
public void InitTree() {
|
||||
for (int i = N + 1; i <= N + 256; i++) Rson[i] = NIL;
|
||||
for (int i = 0; i < N; i++) Dad[i] = NIL;
|
||||
}
|
||||
|
||||
public void InsertNode(int r) {
|
||||
int i, p, cmp;
|
||||
int key = r;
|
||||
cmp = 1;
|
||||
p = N + 1 + buffer[key];
|
||||
Rson[r] = Lson[r] = NIL;
|
||||
MatchLen = 0;
|
||||
|
||||
while (true) {
|
||||
if (cmp >= 0) {
|
||||
if (Rson[p] != NIL)
|
||||
p = Rson[p];
|
||||
else {
|
||||
Rson[p] = r;
|
||||
Dad[r] = p;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (Lson[p] != NIL)
|
||||
p = Lson[p];
|
||||
else {
|
||||
Lson[p] = r;
|
||||
Dad[r] = p;
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (i = 1; i < F; i++)
|
||||
if ((cmp = buffer[key + i] - buffer[p + i]) != 0) break;
|
||||
if (i > MatchLen) {
|
||||
MatchPos = p;
|
||||
if ((MatchLen = i) >= F) break;
|
||||
}
|
||||
}
|
||||
Dad[r] = Dad[p]; Lson[r] = Lson[p]; Rson[r] = Rson[p];
|
||||
Dad[Lson[p]] = r; Dad[Rson[p]] = r;
|
||||
if (Rson[Dad[p]] == p)
|
||||
Rson[Dad[p]] = r;
|
||||
else
|
||||
Lson[Dad[p]] = r;
|
||||
Dad[p] = NIL;
|
||||
}
|
||||
|
||||
public void DeleteNode(int p) {
|
||||
int q;
|
||||
if (Dad[p] == NIL) return;
|
||||
if (Rson[p] == NIL)
|
||||
q = Lson[p];
|
||||
else if (Lson[p] == NIL)
|
||||
q = Rson[p];
|
||||
else {
|
||||
q = Lson[p];
|
||||
if (Rson[q] != NIL) {
|
||||
do {
|
||||
q = Rson[q];
|
||||
} while (Rson[q] != NIL);
|
||||
Rson[Dad[q]] = Lson[q]; Dad[Lson[q]] = Dad[q];
|
||||
Lson[q] = Lson[p]; Dad[Lson[p]] = q;
|
||||
}
|
||||
Rson[q] = Rson[p]; Dad[Rson[p]] = q;
|
||||
}
|
||||
Dad[q] = Dad[p];
|
||||
if (Rson[Dad[p]] == p)
|
||||
Rson[Dad[p]] = q;
|
||||
else
|
||||
Lson[Dad[p]] = q;
|
||||
Dad[p] = NIL;
|
||||
}
|
||||
|
||||
|
||||
public void Encode(Stream input, Stream output) /* was Encode(void) */
|
||||
{
|
||||
int i, c, len, r, s, last_match_length, code_buf_ptr;
|
||||
byte[] code_buf = new byte[17];
|
||||
byte mask;
|
||||
InitTree(); /* initialize trees */
|
||||
code_buf[0] = 0; /* code_buf[1..16] saves eight units of code, and
|
||||
code_buf[0] works as eight flags, "1" representing that the unit
|
||||
is an unencoded letter (1 byte), "0" a position-and-length pair
|
||||
(2 bytes). Thus, eight units require at most 16 bytes of code. */
|
||||
code_buf_ptr = mask = 1;
|
||||
s = 0; r = N - F;
|
||||
for (i = s; i < r; i++) buffer[i] = 0;
|
||||
for (len = 0; len < F && (c = input.ReadByte()) != -1; len++)
|
||||
buffer[r + len] = (byte)c; /* Read F bytes into the last F bytes of
|
||||
the buffer */
|
||||
if (len == 0) return; /* text of size zero */
|
||||
for (i = 1; i <= F; i++) InsertNode(r - i); /* Insert the F strings,
|
||||
each of which begins with one or more 'space' characters. Note
|
||||
the order in which these strings are inserted. This way,
|
||||
degenerate trees will be less likely to occur. */
|
||||
InsertNode(r); /* Finally, insert the whole string just read. The
|
||||
global variables match_length and match_position are set. */
|
||||
do {
|
||||
if (MatchLen > len) MatchLen = len; /* match_length
|
||||
may be spuriously long near the end of text. */
|
||||
if (MatchLen <= THRESHOLD) {
|
||||
MatchLen = 1; /* Not long enough match. Send one byte. */
|
||||
code_buf[0] |= mask; /* 'send one byte' flag */
|
||||
code_buf[code_buf_ptr++] = buffer[r]; /* Send uncoded. */
|
||||
} else {
|
||||
code_buf[code_buf_ptr++] = (byte)MatchPos;
|
||||
code_buf[code_buf_ptr++] = (byte)(((MatchPos >> 4) & 0xf0) | (MatchLen - (THRESHOLD + 1))); /* Send position and
|
||||
length pair. Note match_length > THRESHOLD. */
|
||||
}
|
||||
if ((mask <<= 1) == 0) { /* Shift mask left one bit. */
|
||||
for (i = 0; i < code_buf_ptr; i++) /* Send at most 8 units of */
|
||||
output.WriteByte(code_buf[i]); /* code together */
|
||||
code_buf[0] = 0; code_buf_ptr = mask = 1;
|
||||
}
|
||||
last_match_length = MatchLen;
|
||||
for (i = 0; i < last_match_length &&
|
||||
(c = input.ReadByte()) != -1; i++) {
|
||||
DeleteNode(s); /* Delete old strings and */
|
||||
buffer[s] = (byte)c; /* read new bytes */
|
||||
if (s < F - 1) buffer[s + N] = (byte)c; /* If the position is
|
||||
near the end of buffer, extend the buffer to make
|
||||
string comparison easier. */
|
||||
s = (s + 1) & (N - 1); r = (r + 1) & (N - 1);
|
||||
/* Since this is a ring buffer, increment the position
|
||||
modulo N. */
|
||||
InsertNode(r); /* Register the string in text_buf[r..r+F-1] */
|
||||
}
|
||||
while (i++ < last_match_length) { /* After the end of text, */
|
||||
DeleteNode(s); /* no need to read, but */
|
||||
s = (s + 1) & (N - 1); r = (r + 1) & (N - 1);
|
||||
if ((--len) != 0) InsertNode(r); /* buffer may not be empty. */
|
||||
}
|
||||
} while (len > 0); /* until length of string to be processed is zero */
|
||||
if (code_buf_ptr > 1) { /* Send remaining code. */
|
||||
for (i = 0; i < code_buf_ptr; i++) output.WriteByte(code_buf[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
public void Decode(Stream input, Stream output) /* was Decode(void)
|
||||
Just the reverse of Encode(). */
|
||||
{
|
||||
int i, j, k, r, c;
|
||||
int flags;
|
||||
|
||||
for (i = 0; i < N - F; i++) buffer[i] = 0;
|
||||
r = N - F; flags = 0;
|
||||
for (; ; ) {
|
||||
if (((flags >>= 1) & 256) == 0) {
|
||||
if ((c = input.ReadByte()) == -1) break;
|
||||
flags = c | 0xff00; /* uses higher byte cleverly */
|
||||
} /* to count eight */
|
||||
if ((flags & 1) != 0) {
|
||||
if ((c = input.ReadByte()) == -1) break;
|
||||
output.WriteByte((byte)c); buffer[r++] = (byte)c; r &= (N - 1);
|
||||
} else {
|
||||
if ((i = input.ReadByte()) == -1) break;
|
||||
if ((j = input.ReadByte()) == -1) break;
|
||||
i |= ((j & 0xf0) << 4); j = (j & 0x0f) + THRESHOLD;
|
||||
for (k = 0; k <= j; k++) {
|
||||
c = buffer[(i + k) & (N - 1)];
|
||||
output.WriteByte((byte)c); buffer[r++] = (byte)c; r &= (N - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
<Private>False</Private>
|
||||
<CopyLocalSatelliteAssemblies>False</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\IrosArchive\IrosArchive.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -6,9 +6,11 @@
|
||||
|
||||
using Braver.Plugins;
|
||||
using Braver.Plugins.UI;
|
||||
using IrosArchive;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml;
|
||||
|
||||
namespace Braver._7HShim {
|
||||
@ -21,24 +23,31 @@ namespace Braver._7HShim {
|
||||
protected void SetInt(string name, int i) => _settings[name] = i;
|
||||
protected int GetInt(string name) => _settings.GetValueOrDefault(name);
|
||||
|
||||
private static char[] OP_CHARS = new[] { '=', '<', '>', '!' };
|
||||
internal bool Evaluate(string comparison) {
|
||||
string[] parts = comparison.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 3)
|
||||
throw new InvalidDataException($"Bad comparison: {comparison}");
|
||||
int val1 = _settings.GetValueOrDefault(parts[0]),
|
||||
val2 = int.Parse(parts[2]);
|
||||
int opStart = comparison.IndexOfAny(OP_CHARS),
|
||||
opEnd = comparison.LastIndexOfAny(OP_CHARS);
|
||||
string operand0 = comparison.Substring(0, opStart).Trim(),
|
||||
operand1 = comparison.Substring(opEnd + 1).Trim(),
|
||||
op = comparison.Substring(opStart, opEnd - opStart + 1).Trim();
|
||||
|
||||
if (parts[1] == "=")
|
||||
if (string.IsNullOrEmpty(operand0) || string.IsNullOrEmpty(operand1) || string.IsNullOrEmpty(op))
|
||||
throw new InvalidDataException($"Bad comparison: {comparison}");
|
||||
|
||||
int val1 = _settings.GetValueOrDefault(operand0),
|
||||
val2 = int.Parse(operand1);
|
||||
|
||||
if (op == "=")
|
||||
return val1 == val2;
|
||||
else if (parts[1] == "!=")
|
||||
else if (op == "!=")
|
||||
return val1 != val2;
|
||||
else if (parts[1] == "<")
|
||||
else if (op == "<")
|
||||
return val1 < val2;
|
||||
else if (parts[1] == ">")
|
||||
else if (op == ">")
|
||||
return val1 > val2;
|
||||
else if (parts[1] == "<=")
|
||||
else if (op == "<=")
|
||||
return val1 <= val2;
|
||||
else if (parts[1] == ">=")
|
||||
else if (op == ">=")
|
||||
return val1 >= val2;
|
||||
else
|
||||
throw new InvalidDataException($"Bad comparison: {comparison}");
|
||||
@ -59,26 +68,31 @@ namespace Braver._7HShim {
|
||||
|
||||
public static SevenHConfig Build(XmlNode modinfo, ModuleBuilder module) {
|
||||
var id = Guid.Parse(modinfo.SelectSingleNode("ID").InnerText);
|
||||
var typ = module.DefineType("Config" + id.ToString("N"), System.Reflection.TypeAttributes.Class | System.Reflection.TypeAttributes.Public, typeof(SevenHConfig));
|
||||
var typ = module.DefineType("Config" + id.ToString("N"), TypeAttributes.Class | TypeAttributes.Public, typeof(SevenHConfig));
|
||||
|
||||
var mGetB = typeof(SevenHConfig).GetMethod(nameof(GetBool), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var mSetB = typeof(SevenHConfig).GetMethod(nameof(SetBool), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var mGetI = typeof(SevenHConfig).GetMethod(nameof(GetInt), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var mSetI = typeof(SevenHConfig).GetMethod(nameof(SetInt), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
void DoProp(string propName, Type propType) {
|
||||
void DoProp(string propName, Type propType, string name, string description) {
|
||||
|
||||
MethodInfo mGet = propType == typeof(bool) ? mGetB : mGetI,
|
||||
mSet = propType == typeof(bool) ? mSetB : mSetI;
|
||||
|
||||
var prop = typ.DefineProperty(
|
||||
propName,
|
||||
System.Reflection.PropertyAttributes.None,
|
||||
PropertyAttributes.None,
|
||||
propType, null
|
||||
);
|
||||
var attr = new CustomAttributeBuilder(
|
||||
typeof(ConfigPropertyAttribute).GetConstructor(new[] { typeof(string), typeof(string) }),
|
||||
new object[] { name, description }
|
||||
);
|
||||
prop.SetCustomAttribute(attr);
|
||||
var getBuild = typ.DefineMethod(
|
||||
"get_" + prop.Name,
|
||||
System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.HideBySig | System.Reflection.MethodAttributes.SpecialName,
|
||||
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
|
||||
propType, null
|
||||
);
|
||||
var getIL = getBuild.GetILGenerator();
|
||||
@ -91,7 +105,7 @@ namespace Braver._7HShim {
|
||||
|
||||
var setBuild = typ.DefineMethod(
|
||||
"set_" + prop.Name,
|
||||
System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.HideBySig | System.Reflection.MethodAttributes.SpecialName,
|
||||
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
|
||||
null, new[] { propType }
|
||||
);
|
||||
var setIL = setBuild.GetILGenerator();
|
||||
@ -99,7 +113,7 @@ namespace Braver._7HShim {
|
||||
setIL.Emit(OpCodes.Ldstr, prop.Name);
|
||||
setIL.Emit(OpCodes.Ldarg_1);
|
||||
if (propType.IsEnum)
|
||||
setIL.Emit(OpCodes.Castclass, typeof(int));
|
||||
setIL.Emit(OpCodes.Conv_I4);
|
||||
setIL.Emit(OpCodes.Callvirt, mSet);
|
||||
setIL.Emit(OpCodes.Ret);
|
||||
prop.SetSetMethod(setBuild);
|
||||
@ -107,11 +121,13 @@ namespace Braver._7HShim {
|
||||
|
||||
foreach (XmlNode xoption in modinfo.SelectNodes("ConfigOption")) {
|
||||
string optType = xoption.SelectSingleNode("Type").InnerText;
|
||||
string optName = xoption.SelectSingleNode("ID").InnerText;
|
||||
string optID = xoption.SelectSingleNode("ID").InnerText;
|
||||
string optName = xoption.SelectSingleNode("Name").InnerText;
|
||||
string optDesc = xoption.SelectSingleNode("Description").InnerText;
|
||||
if (optType.Equals("Bool", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
DoProp(optName, typeof(bool));
|
||||
DoProp(optID, typeof(bool), optName, optDesc);
|
||||
} else if (optType.Equals("List", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
var enumtype = module.DefineEnum("List_" + optName, System.Reflection.TypeAttributes.Public, typeof(int));
|
||||
var enumtype = module.DefineEnum("List_" + optID, TypeAttributes.Public, typeof(int));
|
||||
foreach(XmlNode xlistopt in xoption.SelectNodes("Option")) {
|
||||
enumtype.DefineLiteral(
|
||||
xlistopt.Attributes["Name"].Value.Replace(" ", "_"),
|
||||
@ -119,7 +135,7 @@ namespace Braver._7HShim {
|
||||
);
|
||||
}
|
||||
var builtEnum = enumtype.CreateType();
|
||||
DoProp(optName, builtEnum);
|
||||
DoProp(optID, builtEnum, optName, optDesc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,11 +144,97 @@ namespace Braver._7HShim {
|
||||
}
|
||||
}
|
||||
|
||||
internal class SevenHMod {
|
||||
public XmlDocument ModXml { get; set; }
|
||||
internal abstract class SevenHMod {
|
||||
public XmlDocument ModXml { get; protected set; }
|
||||
public int ID { get; set; }
|
||||
public SevenHConfig Config { get; set; }
|
||||
public string Root { get; set; }
|
||||
|
||||
public abstract IEnumerable<string> GetSubFolders(string folder);
|
||||
public abstract IEnumerable<string> GetFolders();
|
||||
public abstract DataSource GetDataSource(string folder, string subfolder);
|
||||
}
|
||||
|
||||
internal class SevenHIroMod : SevenHMod, IDisposable {
|
||||
private IrosArchive.IrosArc _iro;
|
||||
|
||||
public SevenHIroMod(string iroFile) {
|
||||
_iro = new IrosArchive.IrosArc(iroFile);
|
||||
|
||||
var doc = new XmlDocument();
|
||||
using (var s = _iro.GetData("mod.xml"))
|
||||
doc.Load(s);
|
||||
ModXml = doc;
|
||||
}
|
||||
|
||||
private class IroDataSource : DataSource {
|
||||
private IrosArchive.IrosArc _iro;
|
||||
private string _folder;
|
||||
private string[] _filenames;
|
||||
|
||||
public IroDataSource(IrosArc iro, string folder) {
|
||||
_iro = iro;
|
||||
_folder = folder;
|
||||
_filenames = iro.AllFileNames()
|
||||
.Where(s => s.StartsWith(folder))
|
||||
.Select(s => Path.GetFileName(s))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public override IEnumerable<string> Scan() => _filenames;
|
||||
public override Stream TryOpen(string file) => _iro.GetData(_folder + file);
|
||||
|
||||
public override string ToString() => $"IRO {_iro}";
|
||||
}
|
||||
|
||||
public override DataSource GetDataSource(string folder, string subfolder) {
|
||||
if (string.IsNullOrWhiteSpace(subfolder))
|
||||
return new IroDataSource(_iro, folder + "\\");
|
||||
else
|
||||
return new IroDataSource(_iro, folder + "\\" + subfolder + "\\");
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetFolders() {
|
||||
return _iro.AllFolderNames()
|
||||
.Where(s => !s.Contains('\\'));
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetSubFolders(string folder) {
|
||||
string prefix = folder + "\\";
|
||||
return _iro.AllFolderNames()
|
||||
.Where(s => s.StartsWith(prefix))
|
||||
.Select(s => s.Substring(prefix.Length))
|
||||
.Where(s => !s.Contains('\\'));
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_iro.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal class SevenHFileMod : SevenHMod {
|
||||
private string _root;
|
||||
|
||||
public SevenHFileMod(string root) {
|
||||
_root = root;
|
||||
var doc = new XmlDocument();
|
||||
doc.Load(Path.Combine(_root, "mod.xml"));
|
||||
ModXml = doc;
|
||||
}
|
||||
|
||||
public override DataSource GetDataSource(string folder, string subfolder) {
|
||||
return new FileDataSource(Path.Combine(_root, folder, subfolder));
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetFolders() {
|
||||
return Directory.GetDirectories(_root)
|
||||
.Select(fn => Path.GetFileName(fn));
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetSubFolders(string folder) {
|
||||
return Directory.GetDirectories(Path.Combine(_root, folder))
|
||||
.Select(fn => Path.GetFileName(fn));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SevenHConfigCollection {
|
||||
@ -155,6 +257,16 @@ namespace Braver._7HShim {
|
||||
PropertyAttributes.None,
|
||||
config.GetType(), null
|
||||
);
|
||||
|
||||
var attr = new CustomAttributeBuilder(
|
||||
typeof(ConfigPropertyAttribute).GetConstructor(new[] { typeof(string), typeof(string) }),
|
||||
new object[] {
|
||||
mod.ModXml.SelectSingleNode("/ModInfo/Name").InnerText,
|
||||
mod.ModXml.SelectSingleNode("/ModInfo/Description").InnerText
|
||||
}
|
||||
);
|
||||
prop.SetCustomAttribute(attr);
|
||||
|
||||
var getBuild = typ.DefineMethod(
|
||||
"get_" + prop.Name,
|
||||
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
|
||||
@ -214,13 +326,16 @@ namespace Braver._7HShim {
|
||||
if (File.Exists(modxml)) {
|
||||
var doc = new XmlDocument();
|
||||
doc.Load(modxml);
|
||||
_mods.Add(new SevenHMod {
|
||||
ModXml = doc,
|
||||
_mods.Add(new SevenHFileMod(subdirectory) {
|
||||
ID = _mods.Count + 1,
|
||||
Root = subdirectory,
|
||||
});
|
||||
}
|
||||
}
|
||||
foreach(string iro in Directory.GetFiles(thisFolder, "*.iro")) {
|
||||
_mods.Add(new SevenHIroMod(iro) {
|
||||
ID = _mods.Count + 1
|
||||
});
|
||||
}
|
||||
|
||||
AssemblyName assemblyName = new AssemblyName("SevenHShim.DynamicConfig");
|
||||
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
|
||||
@ -239,7 +354,10 @@ namespace Braver._7HShim {
|
||||
|
||||
public override void Init(BGame game) {
|
||||
foreach(var mod in _mods) {
|
||||
HashSet<string> remainingFolders = new HashSet<string>(mod.GetFolders(), StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach(XmlNode xfolder in mod.ModXml.SelectNodes("/ModInfo/ModFolder")) {
|
||||
string folder = xfolder.Attributes["Folder"].Value;
|
||||
remainingFolders.Remove(folder);
|
||||
bool isActive;
|
||||
if (xfolder.Attributes["ActiveWhen"] != null)
|
||||
isActive = mod.Config.Evaluate(xfolder.Attributes["ActiveWhen"].Value);
|
||||
@ -248,11 +366,12 @@ namespace Braver._7HShim {
|
||||
else
|
||||
isActive = true;
|
||||
if (isActive) {
|
||||
string folder = Path.Combine(mod.Root, xfolder.Attributes["Folder"].Value);
|
||||
foreach (string datafolder in Directory.GetDirectories(folder))
|
||||
game.AddDataSource(Path.GetFileName(datafolder), new FileDataSource(datafolder));
|
||||
foreach (string datafolder in mod.GetSubFolders(folder))
|
||||
game.AddDataSource(Path.GetFileName(datafolder), mod.GetDataSource(folder, datafolder));
|
||||
}
|
||||
}
|
||||
foreach (string folder in remainingFolders)
|
||||
game.AddDataSource(Path.GetFileName(folder), mod.GetDataSource(folder, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ using Tommy;
|
||||
namespace Braver.FFNxCompatibility {
|
||||
|
||||
public class FFNxConfig {
|
||||
[ConfigProperty("Enable Field Ambient Sounds")]
|
||||
public bool FieldAmbientSounds { get; set; } = true;
|
||||
}
|
||||
|
||||
|
@ -16,8 +16,11 @@ using System.Windows.Forms;
|
||||
namespace Braver.Tolk {
|
||||
|
||||
public class TolkConfig {
|
||||
[ConfigProperty("Enable SAPI support")]
|
||||
public bool EnableSAPI { get; set; } = true;
|
||||
[ConfigProperty("Enable Footstep Sounds")]
|
||||
public bool EnableFootsteps { get; set; } = true;
|
||||
[ConfigProperty("Enable Focus sounds")]
|
||||
public bool EnableFocusTracking { get; set; } = true;
|
||||
}
|
||||
|
||||
@ -79,7 +82,7 @@ namespace Braver.Tolk {
|
||||
private object _lastMenuContainer = null;
|
||||
public void Menu(IEnumerable<string> items, int selected, object container) {
|
||||
DavyKager.Tolk.Speak(
|
||||
$"Menu {items.ElementAtOrDefault(selected)}, {selected + 1} of {items.Count()}",
|
||||
$"{items.ElementAtOrDefault(selected)}, {selected + 1} of {items.Count()}",
|
||||
_lastMenuContainer == container
|
||||
);
|
||||
_lastMenuContainer = container;
|
||||
|
Loading…
Reference in New Issue
Block a user