mirror of
https://github.com/crskycode/GARbro.git
synced 2026-06-14 09:44:20 +08:00
Compare commits
108 Commits
GARbro-Mod
...
GARbro-Mod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f63834c4bf | ||
|
|
8f96a1bccd | ||
|
|
f9def97321 | ||
|
|
301b795de6 | ||
|
|
cd8fcea992 | ||
|
|
ddbeddd862 | ||
|
|
82d44aeae2 | ||
|
|
a7021686e9 | ||
|
|
cdd4e0161c | ||
|
|
8424d75eab | ||
|
|
9cb5ec89b3 | ||
|
|
ac063d7eb5 | ||
|
|
7210ff5ff3 | ||
|
|
ccc7c8032b | ||
|
|
499ac02c64 | ||
|
|
a20aa9434c | ||
|
|
b967d505ec | ||
|
|
4554b82be5 | ||
|
|
e642644845 | ||
|
|
4a16bb3e9e | ||
|
|
6eae25b574 | ||
|
|
a8d73d24bb | ||
|
|
ab7b799c7d | ||
|
|
ba065f76af | ||
|
|
afd4d5f7fa | ||
|
|
d76abea03c | ||
|
|
08e5a28058 | ||
|
|
d4f7fa2eed | ||
|
|
5915546fdf | ||
|
|
3120d9477b | ||
|
|
c00f8621da | ||
|
|
ca196d8286 | ||
|
|
7bd244e228 | ||
|
|
d621cf4c03 | ||
|
|
7b3bb7e730 | ||
|
|
092b544c87 | ||
|
|
52878652ef | ||
|
|
583249c955 | ||
|
|
2d930b2191 | ||
|
|
ad7f4f1788 | ||
|
|
329589122a | ||
|
|
d2a22fa2fb | ||
|
|
0817dacd99 | ||
|
|
ed0f0489d3 | ||
|
|
57749e5cac | ||
|
|
4773ff148f | ||
|
|
2ad5095cec | ||
|
|
3f4c263ec5 | ||
|
|
4d5be92386 | ||
|
|
e3236c0fbd | ||
|
|
681e82dd3f | ||
|
|
071a33eb94 | ||
|
|
ab67cb5187 | ||
|
|
aa2900f15b | ||
|
|
881e5f1e3f | ||
|
|
beaab0ecee | ||
|
|
45e7af60ea | ||
|
|
738b2950f6 | ||
|
|
6a90018798 | ||
|
|
3bd697577c | ||
|
|
30e04eae1f | ||
|
|
b3f5195a9a | ||
|
|
2e8057aa54 | ||
|
|
ddabe6d2f4 | ||
|
|
2a6f3ab70c | ||
|
|
e2d4ddb59a | ||
|
|
8fcb85334e | ||
|
|
7f74a030a2 | ||
|
|
682a5fd2e7 | ||
|
|
13f2923dc4 | ||
|
|
92b809e95b | ||
|
|
c1008aba3e | ||
|
|
b68b11a1a2 | ||
|
|
4124b369de | ||
|
|
92c54b72ae | ||
|
|
3c01e689a6 | ||
|
|
e69ce728ec | ||
|
|
54ca6d8f1f | ||
|
|
64a88dc045 | ||
|
|
741f50619e | ||
|
|
824ea431de | ||
|
|
200a05db81 | ||
|
|
eb60e27378 | ||
|
|
53d8fdd844 | ||
|
|
718ceb4c40 | ||
|
|
240bb39f99 | ||
|
|
a52d2f3e25 | ||
|
|
0434623308 | ||
|
|
30b875efe8 | ||
|
|
da4b6574da | ||
|
|
3443a2d16f | ||
|
|
d6259d4535 | ||
|
|
5292d3c9c8 | ||
|
|
b1cc4eddf3 | ||
|
|
da00eee168 | ||
|
|
d4cde67f43 | ||
|
|
8188874e81 | ||
|
|
4a3f4562b0 | ||
|
|
6efe8ad30b | ||
|
|
dad693ca9e | ||
|
|
85962ae01f | ||
|
|
e55dd019fc | ||
|
|
5a9a873c4b | ||
|
|
797816f856 | ||
|
|
df2ebbc7c5 | ||
|
|
7d4c61e91a | ||
|
|
4a81140a2c | ||
|
|
66dde11935 |
25
.github/workflows/build.yml
vendored
Normal file
25
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: .NET Framework Build
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
# Restore NuGet packages
|
||||
- name: Restore
|
||||
run: nuget restore
|
||||
# Build the solution
|
||||
- name: Build
|
||||
run: msbuild /p:Configuration=Release /p:Platform="Any CPU" /p:TargetFrameworkVersion=v4.6.2 GARbro.sln
|
||||
# Publish the artifacts
|
||||
- name: Publish Artifact
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: GARbro-Mod-ci-build
|
||||
path: bin/Release
|
||||
@@ -14,6 +14,8 @@
|
||||
<TargetFrameworkProfile />
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -102,6 +104,9 @@
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="ZstdNet, Version=1.4.5.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ZstdNet.1.4.5\lib\net45\ZstdNet.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Abel\ArcARC.cs" />
|
||||
@@ -123,12 +128,24 @@
|
||||
<Compile Include="AdvSys\ImageGWD.cs" />
|
||||
<Compile Include="Ail\ArcLNK2.cs" />
|
||||
<Compile Include="AIRNovel\ArcAIR.cs" />
|
||||
<Compile Include="Artemis\ImageNekoPNG.cs" />
|
||||
<Compile Include="CsWare\AudioWAV.cs" />
|
||||
<Compile Include="CsWare\ImageGDT.cs" />
|
||||
<Compile Include="DigitalWorks\ArcPACPS2.cs" />
|
||||
<Compile Include="DxLib\HuffmanDecoder.cs" />
|
||||
<Compile Include="DxLib\WidgetDXA.xaml.cs">
|
||||
<DependentUpon>WidgetDXA.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="FC01\ArcBDT.cs" />
|
||||
<Compile Include="FC01\ArcSCXA.cs" />
|
||||
<Compile Include="FC01\BdtTables.cs" />
|
||||
<Compile Include="GScripter\ArcDATA.cs" />
|
||||
<Compile Include="Ism\ImagePNG.cs" />
|
||||
<Compile Include="Kid\ArcDATRAW.cs" />
|
||||
<Compile Include="Kid\ImageBIP.cs" />
|
||||
<Compile Include="Kid\ImageBIParc.cs" />
|
||||
<Compile Include="Kid\ImageLBG.cs" />
|
||||
<Compile Include="Kid\ImageSPC.cs" />
|
||||
<Compile Include="Kogado\ArcARC.cs" />
|
||||
<Compile Include="Ice\ImageIBM.cs" />
|
||||
<Compile Include="Ice\ScriptISD.cs" />
|
||||
@@ -168,7 +185,17 @@
|
||||
<Compile Include="Macromedia\AudioSND.cs" />
|
||||
<Compile Include="Macromedia\DirectorFile.cs" />
|
||||
<Compile Include="Macromedia\Palettes.cs" />
|
||||
<Compile Include="MAGES\ArcARC20.cs" />
|
||||
<Compile Include="MAGES\ArcFARC.cs" />
|
||||
<Compile Include="MAGES\ArcGPDA.cs" />
|
||||
<Compile Include="MAGES\ArcGTF.cs" />
|
||||
<Compile Include="MAGES\ArcLoveOnce.cs" />
|
||||
<Compile Include="MAGES\ImageBIN.cs" />
|
||||
<Compile Include="Mugi\ArcBIN.cs" />
|
||||
<Compile Include="Musica\ArcPAK.cs" />
|
||||
<Compile Include="NipponIchi\ArcCASN.cs" />
|
||||
<Compile Include="NipponIchi\ArcPSFS.cs" />
|
||||
<Compile Include="NipponIchi\ImageNMT.cs" />
|
||||
<Compile Include="NScripter\Script.cs" />
|
||||
<Compile Include="Psp\ArcQPK.cs" />
|
||||
<Compile Include="ScrPlayer\ImageIMG.cs" />
|
||||
@@ -309,8 +336,8 @@
|
||||
<Compile Include="MyAdv\ArcPAC.cs" />
|
||||
<Compile Include="Nekopack\ArcNEKO2.cs" />
|
||||
<Compile Include="Nekopack\ArcNEKO3.cs" />
|
||||
<Compile Include="NitroPlus\ArcLAY.cs" />
|
||||
<Compile Include="NitroPlus\ArcMPK.cs" />
|
||||
<Compile Include="MAGES\ArcLAY.cs" />
|
||||
<Compile Include="MAGES\ArcMPK.cs" />
|
||||
<Compile Include="NitroPlus\ArcNPP.cs" />
|
||||
<Compile Include="Noesis\ArcIGA.cs" />
|
||||
<Compile Include="NonColor\ArcMinato.cs" />
|
||||
@@ -1127,6 +1154,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="DxLib\WidgetDXA.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="DxLib\WidgetSCR.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -1298,6 +1329,7 @@ exit 0</PreBuildEvent>
|
||||
<PostBuildEvent>if not exist "$(TargetDir)\GameData" mkdir "$(TargetDir)\GameData"
|
||||
xcopy "$(ProjectDir)\Resources\Formats.dat" "$(TargetDir)\GameData\" /D /Y >NUL</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\packages\ZstdNet.1.4.5\build\ZstdNet.targets" Condition="Exists('..\packages\ZstdNet.1.4.5\build\ZstdNet.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
165
ArcFormats/Artemis/ImageNekoPNG.cs
Normal file
165
ArcFormats/Artemis/ImageNekoPNG.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
//! \file ImageNekoPNG.cs
|
||||
//! \date Sun Nov 19 15:40:46 2023
|
||||
//! \brief NekoNyan PNG image format.
|
||||
//
|
||||
// Copyright (C) 2016 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.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media;
|
||||
using System.Windows;
|
||||
|
||||
namespace GameRes.Formats.Artemis
|
||||
{
|
||||
internal class NekoPNGMetaData : ImageMetaData
|
||||
{
|
||||
public int[] Offset;
|
||||
public int[] Length;
|
||||
}
|
||||
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class ImageNekoPNG : ImageFormat
|
||||
{
|
||||
public override string Tag { get { return "PNG"; } }
|
||||
public override string Description { get { return "NekoNyan PNG image format"; } }
|
||||
public override uint Signature { get { return 0x00000040; } }
|
||||
|
||||
public override ImageData Read (IBinaryStream file, ImageMetaData info)
|
||||
{
|
||||
var metadata = (NekoPNGMetaData)info;
|
||||
var bitmap = new WriteableBitmap ((int)info.Width, (int)info.Height, ImageData.DefaultDpiX, ImageData.DefaultDpiY, PixelFormats.Bgra32, null);
|
||||
bitmap.Lock ();
|
||||
try
|
||||
{
|
||||
var output = bitmap.BackBuffer;
|
||||
int stride = bitmap.BackBufferStride;
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
file.Position = metadata.Offset[i];
|
||||
var data = file.ReadBytes (metadata.Length[i]);
|
||||
for (var j = 0; j < 16; j++)
|
||||
data[j] -= (byte)(0x77 * j);
|
||||
var slice_width = 0;
|
||||
var slice_height = 0;
|
||||
if (1 != WebPGetInfo (data, (UIntPtr)data.Length, ref slice_width, ref slice_height))
|
||||
throw new InvalidFormatException("WebP image decoder failed.");
|
||||
var slice_size = stride * slice_height;
|
||||
if (IntPtr.Zero == WebPDecodeBGRAInto (data, (UIntPtr)data.Length, output, (UIntPtr)slice_size, stride))
|
||||
throw new InvalidFormatException("WebP image decoder failed.");
|
||||
output += slice_size;
|
||||
}
|
||||
bitmap.AddDirtyRect (new Int32Rect(0, 0, (int)info.Width, (int)info.Height));
|
||||
}
|
||||
finally
|
||||
{
|
||||
bitmap.Unlock();
|
||||
}
|
||||
bitmap.Freeze();
|
||||
return new ImageData (bitmap, info);
|
||||
}
|
||||
|
||||
public override ImageMetaData ReadMetaData (IBinaryStream file)
|
||||
{
|
||||
if (file.Signature != 0x40)
|
||||
return null;
|
||||
if (file.Length <= 0x40)
|
||||
return null;
|
||||
var offset = new int[8];
|
||||
var length = new int[8];
|
||||
for (var i = 0; i < 8; i++)
|
||||
offset[i] = file.ReadInt32();
|
||||
for (var i = 0; i < 8; i++)
|
||||
length[i] = file.ReadInt32();
|
||||
LibWebPLoader.Load();
|
||||
var image_width = 0;
|
||||
var image_height = 0;
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
if (offset[i] < 64)
|
||||
return null;
|
||||
if (length[i] < 32)
|
||||
return null;
|
||||
if (offset[i] + length[i] > file.Length)
|
||||
return null;
|
||||
file.Position = offset[i];
|
||||
var webp_header = file.ReadBytes (32);
|
||||
for (var j = 0; j < 16; j++)
|
||||
webp_header[j] -= (byte)(0x77 * j);
|
||||
var slice_width = 0;
|
||||
var slice_height = 0;
|
||||
if (1 != WebPGetInfo (webp_header, (UIntPtr)webp_header.Length, ref slice_width, ref slice_height))
|
||||
throw new InvalidFormatException ("WebP image decoder failed.");
|
||||
image_width = Math.Max (image_width, slice_width);
|
||||
image_height += slice_height;
|
||||
}
|
||||
return new NekoPNGMetaData
|
||||
{
|
||||
Width = (uint)image_width,
|
||||
Height = (uint)image_height,
|
||||
BPP = 32,
|
||||
Offset = offset,
|
||||
Length = length,
|
||||
};
|
||||
}
|
||||
|
||||
public override void Write (Stream file, ImageData bitmap)
|
||||
{
|
||||
throw new NotImplementedException("ImageNekoPNG.Write not implemented");
|
||||
}
|
||||
|
||||
[DllImport("libwebp.dll", EntryPoint = "WebPGetInfo", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int WebPGetInfo ([MarshalAs(UnmanagedType.LPArray)] byte[] data, UIntPtr data_size, ref int width, ref int height);
|
||||
|
||||
[DllImport("libwebp.dll", EntryPoint = "WebPDecodeBGRAInto", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr WebPDecodeBGRAInto ([MarshalAs(UnmanagedType.LPArray)] byte[] data, UIntPtr data_size, IntPtr output_buffer, UIntPtr output_buffer_size, int output_stride);
|
||||
}
|
||||
|
||||
internal static class LibWebPLoader
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
|
||||
static extern IntPtr LoadLibraryEx (string lpFileName, IntPtr hReservedNull, uint dwFlags);
|
||||
|
||||
static bool loaded = false;
|
||||
|
||||
const uint LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100;
|
||||
const uint LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800;
|
||||
|
||||
public static void Load ()
|
||||
{
|
||||
if (loaded)
|
||||
return;
|
||||
var folder = Path.GetDirectoryName (Assembly.GetExecutingAssembly().Location);
|
||||
folder = Path.Combine (folder, (IntPtr.Size == 4) ? "x86" : "x64");
|
||||
var fullPath = Path.Combine (folder, "libwebp.dll");
|
||||
fullPath = Path.GetFullPath (fullPath);
|
||||
var handle = LoadLibraryEx (fullPath, IntPtr.Zero, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
if (IntPtr.Zero == handle)
|
||||
throw new Win32Exception (Marshal.GetLastWin32Error());
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,53 @@
|
||||
//! \file ArcGXP.cs
|
||||
//! \date Thu Jun 30 08:17:12 2016
|
||||
//! \brief Astronauts resource archive.
|
||||
//
|
||||
// Copyright (C) 2016 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 System.Text;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Astronauts
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class PakOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "GXP"; } }
|
||||
public override string Description { get { return "Astronauts resource archive"; } }
|
||||
public override uint Signature { get { return 0x505847; } } // 'GXP'
|
||||
public override bool IsHierarchic { get { return true; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
private bool UseNameAsKey = false;
|
||||
|
||||
static readonly byte[] KnownKey = {
|
||||
0x40, 0x21, 0x28, 0x38, 0xA6, 0x6E, 0x43, 0xA5, 0x40, 0x21, 0x28, 0x38, 0xA6, 0x43, 0xA5, 0x64,
|
||||
0x3E, 0x65, 0x24, 0x20, 0x46, 0x6E, 0x74,
|
||||
};
|
||||
|
||||
//! \file ArcGXP.cs
|
||||
//! \date Thu Jun 30 08:17:12 2016
|
||||
//! \brief Astronauts resource archive.
|
||||
//
|
||||
// Copyright (C) 2016 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 System.Text;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Astronauts
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class PakOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "GXP"; } }
|
||||
public override string Description { get { return "Astronauts resource archive"; } }
|
||||
public override uint Signature { get { return 0x505847; } } // 'GXP'
|
||||
public override bool IsHierarchic { get { return true; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
private bool UseNameAsKey = false;
|
||||
|
||||
static readonly byte[] KnownKey = {
|
||||
0x40, 0x21, 0x28, 0x38, 0xA6, 0x6E, 0x43, 0xA5, 0x40, 0x21, 0x28, 0x38, 0xA6, 0x43, 0xA5, 0x64,
|
||||
0x3E, 0x65, 0x24, 0x20, 0x46, 0x6E, 0x74,
|
||||
};
|
||||
|
||||
public override ArcFile TryOpen(ArcView file)
|
||||
{
|
||||
UseNameAsKey = false;
|
||||
@@ -58,76 +58,76 @@ namespace GameRes.Formats.Astronauts
|
||||
arc = TryOpenGxpFile(file);
|
||||
}
|
||||
return arc;
|
||||
}
|
||||
|
||||
private ArcFile TryOpenGxpFile (ArcView file)
|
||||
{
|
||||
int count = file.View.ReadInt32 (0x18);
|
||||
if (!IsSaneCount (count))
|
||||
}
|
||||
|
||||
private ArcFile TryOpenGxpFile (ArcView file)
|
||||
{
|
||||
int count = file.View.ReadInt32 (0x18);
|
||||
if (!IsSaneCount (count))
|
||||
return null;
|
||||
string arcname = Path.GetFileName(file.Name);
|
||||
byte[] arcname_bytes = Encoding.ASCII.GetBytes(arcname);
|
||||
long base_offset = file.View.ReadInt64 (0x28);
|
||||
uint entry_key = KnownKey[0] | (1u ^ KnownKey[1]) << 8 | (2u ^ KnownKey[2]) << 16 | (3u ^ KnownKey[3]) << 24;
|
||||
string arcname = Path.GetFileName(file.Name);
|
||||
byte[] arcname_bytes = Encoding.ASCII.GetBytes(arcname);
|
||||
long base_offset = file.View.ReadInt64 (0x28);
|
||||
uint entry_key = KnownKey[0] | (1u ^ KnownKey[1]) << 8 | (2u ^ KnownKey[2]) << 16 | (3u ^ KnownKey[3]) << 24;
|
||||
if (UseNameAsKey)
|
||||
{
|
||||
uint arcname_key = (uint)(arcname_bytes[0] | (arcname_bytes[1 % arcname_bytes.Length]) << 8 | (arcname_bytes[2 % arcname_bytes.Length]) << 16 | (arcname_bytes[3 % arcname_bytes.Length]) << 24);
|
||||
entry_key ^= arcname_key;
|
||||
}
|
||||
uint index_offset = 0x30;
|
||||
var entry_buffer = new byte[0x100];
|
||||
var dir = new List<Entry> (count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
var entry_length = file.View.ReadUInt32 (index_offset) ^ entry_key;
|
||||
if (entry_length < 0x20 || entry_length > 0x1000)
|
||||
return null;
|
||||
if (entry_length > entry_buffer.Length)
|
||||
entry_buffer = new byte[entry_length];
|
||||
if (entry_length != file.View.Read (index_offset, entry_buffer, 0, entry_length))
|
||||
return null;
|
||||
}
|
||||
uint index_offset = 0x30;
|
||||
var entry_buffer = new byte[0x100];
|
||||
var dir = new List<Entry> (count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
var entry_length = file.View.ReadUInt32 (index_offset) ^ entry_key;
|
||||
if (entry_length < 0x20 || entry_length > 0x1000)
|
||||
return null;
|
||||
if (entry_length > entry_buffer.Length)
|
||||
entry_buffer = new byte[entry_length];
|
||||
if (entry_length != file.View.Read (index_offset, entry_buffer, 0, entry_length))
|
||||
return null;
|
||||
if (UseNameAsKey)
|
||||
Decrypt(entry_buffer, entry_length, arcname_bytes);
|
||||
else
|
||||
Decrypt(entry_buffer, entry_length);
|
||||
int name_length = LittleEndian.ToInt32 (entry_buffer, 0xC) * 2; // length in characters
|
||||
if (name_length >= entry_length)
|
||||
return null;
|
||||
var name = Encoding.Unicode.GetString (entry_buffer, 0x20, name_length);
|
||||
var entry = FormatCatalog.Instance.Create<Entry> (name);
|
||||
entry.Offset = base_offset + LittleEndian.ToInt64 (entry_buffer, 0x18);
|
||||
entry.Size = LittleEndian.ToUInt32 (entry_buffer, 4); // length is 64-bit actually
|
||||
if (!entry.CheckPlacement (file.MaxOffset))
|
||||
return null;
|
||||
dir.Add (entry);
|
||||
index_offset += entry_length;
|
||||
}
|
||||
return new ArcFile (file, this, dir);
|
||||
}
|
||||
|
||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||
{
|
||||
var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
|
||||
Decrypt(entry_buffer, entry_length);
|
||||
int name_length = LittleEndian.ToInt32 (entry_buffer, 0xC) * 2; // length in characters
|
||||
if (name_length >= entry_length)
|
||||
return null;
|
||||
var name = Encoding.Unicode.GetString (entry_buffer, 0x20, name_length);
|
||||
var entry = FormatCatalog.Instance.Create<Entry> (name);
|
||||
entry.Offset = base_offset + LittleEndian.ToInt64 (entry_buffer, 0x18);
|
||||
entry.Size = LittleEndian.ToUInt32 (entry_buffer, 4); // length is 64-bit actually
|
||||
if (!entry.CheckPlacement (file.MaxOffset))
|
||||
return null;
|
||||
dir.Add (entry);
|
||||
index_offset += entry_length;
|
||||
}
|
||||
return new ArcFile (file, this, dir);
|
||||
}
|
||||
|
||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||
{
|
||||
var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
|
||||
if (UseNameAsKey)
|
||||
{
|
||||
string arcname = Path.GetFileName(arc.File.Name);
|
||||
byte[] arcname_bytes = Encoding.ASCII.GetBytes(arcname);
|
||||
Decrypt(data, entry.Size, arcname_bytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
Decrypt(data, entry.Size);
|
||||
return new BinMemoryStream (data, entry.Name);
|
||||
}
|
||||
|
||||
static void Decrypt (byte[] data, uint length, byte[] key=null)
|
||||
{
|
||||
for (uint i = 0; i < length; ++i)
|
||||
{
|
||||
byte xorkey = (byte)(i ^ KnownKey[i % KnownKey.Length]);
|
||||
Decrypt(data, entry.Size);
|
||||
return new BinMemoryStream (data, entry.Name);
|
||||
}
|
||||
|
||||
static void Decrypt (byte[] data, uint length, byte[] key=null)
|
||||
{
|
||||
for (uint i = 0; i < length; ++i)
|
||||
{
|
||||
byte xorkey = (byte)(i ^ KnownKey[i % KnownKey.Length]);
|
||||
if(null != key)
|
||||
xorkey ^= key[i % key.Length];
|
||||
data[i] ^= xorkey;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
xorkey ^= key[i % key.Length];
|
||||
data[i] ^= xorkey;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,12 +42,12 @@ namespace GameRes.Formats.Cri
|
||||
|
||||
public SpcOpener ()
|
||||
{
|
||||
Extensions = new string[] { "spc" };
|
||||
Extensions = new string[] { "spc", "bip" };
|
||||
}
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
if (!file.Name.HasExtension (".spc"))
|
||||
if (!file.Name.HasExtension (".spc") && !file.Name.HasExtension(".bip"))
|
||||
return null;
|
||||
uint unpacked_size = file.View.ReadUInt32 (0);
|
||||
if (unpacked_size <= 0x20 || unpacked_size > 0x5000000)
|
||||
@@ -57,9 +57,9 @@ namespace GameRes.Formats.Cri
|
||||
backend.Position = 4;
|
||||
var lzss = new LzssStream (backend);
|
||||
var input = new SeekableStream (lzss);
|
||||
var base_name = Path.GetFileNameWithoutExtension(file.Name);
|
||||
try
|
||||
{
|
||||
var base_name = Path.GetFileNameWithoutExtension (file.Name);
|
||||
using (var spc = new XtxIndexBuilder (input, base_name))
|
||||
{
|
||||
spc.ReadIndex (0);
|
||||
@@ -71,8 +71,15 @@ namespace GameRes.Formats.Cri
|
||||
}
|
||||
catch
|
||||
{
|
||||
input.Dispose();
|
||||
throw;
|
||||
//input.Dispose();
|
||||
//throw;
|
||||
var dir = new List<Entry>();
|
||||
var entry = Create<PackedEntry>(base_name);
|
||||
entry.Offset = 0;
|
||||
entry.Size = (uint)input.Length;
|
||||
entry.Type = "image";
|
||||
dir.Add(entry);
|
||||
return new SpcArchive(file, this, dir, input);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace GameRes.Formats.Cri
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class SpcFormat : XtxFormat
|
||||
{
|
||||
public override string Tag { get { return "SPC"; } }
|
||||
public override string Tag { get { return "SPC/Xbox360"; } }
|
||||
public override string Description { get { return "CRI MiddleWare compressed texture format"; } }
|
||||
public override uint Signature { get { return 0; } }
|
||||
|
||||
|
||||
@@ -35,20 +35,20 @@ namespace GameRes.Formats.DigitalWorks
|
||||
{
|
||||
public override string Tag { get { return "PAC/HED"; } }
|
||||
public override string Description { get { return "Digital Works resource archive"; } }
|
||||
public override uint Signature { get { return 0x43415050; } } // 'PPAC-PAC'
|
||||
public override uint Signature { get { return 0; } } // 'PPAC-PAC'
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
if (!file.View.AsciiEqual (4, "-PAC"))
|
||||
if (!file.View.AsciiEqual (0, "PPAC-PAC") && !file.View.AsciiEqual(0, "FANA_V1.0.0.0"))
|
||||
return null;
|
||||
var hed_name = Path.ChangeExtension (file.Name, "hed");
|
||||
if (!VFS.FileExists (hed_name))
|
||||
return null;
|
||||
using (var hed = VFS.OpenView (hed_name))
|
||||
{
|
||||
if (!hed.View.AsciiEqual (0, "PPAC-HED"))
|
||||
if (!hed.View.AsciiEqual (0, "PPAC-HED") && !hed.View.AsciiEqual(0, "FANA_V1.0.0.0"))
|
||||
return null;
|
||||
uint index_offset = 0x10;
|
||||
const uint data_offset = 0x10;
|
||||
|
||||
98
ArcFormats/DigitalWorks/ArcPACPS2.cs
Normal file
98
ArcFormats/DigitalWorks/ArcPACPS2.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
//! \file ArcPACPS2.cs
|
||||
//! \date 2018 Sep 18
|
||||
//! \brief Digital Works PS2 resource archive.
|
||||
//
|
||||
// Copyright (C) 2018 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.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using GameRes.Compression;
|
||||
|
||||
namespace GameRes.Formats.DigitalWorks
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class PacPS2Opener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "PAC/PS2"; } }
|
||||
public override string Description { get { return "Digital Works PS2 resource archive"; } }
|
||||
public override uint Signature { get { return 0x434150; } } // 'PAC'
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
/**
|
||||
Target games:
|
||||
Cafe Little Wish SLPM-65294
|
||||
F Fanatic SLPM-65296
|
||||
*/
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
uint filename_index = file.View.ReadUInt32(4);
|
||||
int count = file.View.ReadInt32(8);
|
||||
uint index_offset = 0x0C;
|
||||
|
||||
if (!IsSaneCount (count))
|
||||
return null;
|
||||
|
||||
var dir = new List<Entry> (count);
|
||||
int i = 0;
|
||||
while (i < count)
|
||||
{
|
||||
var name = file.View.ReadString (filename_index + i * 0x40, 0x40);
|
||||
var entry = FormatCatalog.Instance.Create<PackedEntry> (name);
|
||||
entry.Offset = file.View.ReadUInt32 (index_offset + i * 8);
|
||||
entry.Size = file.View.ReadUInt32 (index_offset + i * 8 + 4);
|
||||
if (!entry.CheckPlacement (file.MaxOffset))
|
||||
return null;
|
||||
dir.Add (entry);
|
||||
i++;
|
||||
}
|
||||
return new ArcFile (file, this, dir);
|
||||
}
|
||||
|
||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||
{
|
||||
var pent = entry as PackedEntry;
|
||||
if (null == pent)
|
||||
return base.OpenEntry (arc, entry);
|
||||
if (!pent.IsPacked)
|
||||
{
|
||||
if (!arc.File.View.AsciiEqual (entry.Offset, "LZS\0"))
|
||||
return base.OpenEntry (arc, entry);
|
||||
pent.IsPacked = true;
|
||||
pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset+4);
|
||||
}
|
||||
var input = arc.File.CreateStream (entry.Offset+8, entry.Size-8);
|
||||
bool embedded_lzs = (input.Signature & ~0xF0u) == 0x535A4C0F; // 'LZS'
|
||||
var lzs = new LzssStream (input);
|
||||
if (embedded_lzs)
|
||||
{
|
||||
var header = new byte[8];
|
||||
lzs.Read (header, 0, 8);
|
||||
pent.UnpackedSize = header.ToUInt32 (4);
|
||||
lzs = new LzssStream (lzs);
|
||||
}
|
||||
return lzs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ namespace GameRes.Formats.DxLib
|
||||
}
|
||||
}
|
||||
|
||||
byte[] Unpack (Stream stream)
|
||||
protected byte[] Unpack (Stream stream)
|
||||
{
|
||||
using (var input = new ArcView.Reader (stream))
|
||||
{
|
||||
@@ -262,7 +262,7 @@ namespace GameRes.Formats.DxLib
|
||||
}
|
||||
}
|
||||
|
||||
List<Entry> ReadIndex (ArcView file, int version, byte[] key)
|
||||
protected List<Entry> ReadIndex (ArcView file, int version, byte[] key)
|
||||
{
|
||||
DxHeader dx = null;
|
||||
if (version <= 4)
|
||||
@@ -271,7 +271,7 @@ namespace GameRes.Formats.DxLib
|
||||
dx = ReadArcHeaderV6 (file, version, key);
|
||||
if (null == dx || dx.DirTable >= dx.IndexSize || dx.FileTable >= dx.IndexSize)
|
||||
return null;
|
||||
using (var encrypted = file.CreateStream (dx.IndexOffset, dx.IndexSize))
|
||||
using (var encrypted = file.CreateStream (dx.IndexOffset, (uint)dx.IndexSize))
|
||||
using (var index = new EncryptedStream (encrypted, version >= 6 ? 0 : dx.IndexOffset, key))
|
||||
using (var reader = IndexReader.Create (dx, version, index))
|
||||
{
|
||||
@@ -305,21 +305,23 @@ namespace GameRes.Formats.DxLib
|
||||
IndexSize = LittleEndian.ToUInt32 (header, 0),
|
||||
BaseOffset = LittleEndian.ToInt64 (header, 4),
|
||||
IndexOffset = LittleEndian.ToInt64 (header, 0x0C),
|
||||
FileTable = (uint)LittleEndian.ToInt64 (header, 0x14),
|
||||
DirTable = (uint)LittleEndian.ToInt64 (header, 0x1C),
|
||||
FileTable = LittleEndian.ToInt64 (header, 0x14),
|
||||
DirTable = LittleEndian.ToInt64 (header, 0x1C),
|
||||
CodePage = LittleEndian.ToInt32 (header, 0x24),
|
||||
};
|
||||
}
|
||||
|
||||
internal static void Decrypt (byte[] data, int index, int count, long offset, byte[] key)
|
||||
{
|
||||
int key_pos = (int)(offset % key.Length);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
data[index+i] ^= key[key_pos++];
|
||||
if (key.Length == key_pos)
|
||||
key_pos = 0;
|
||||
}
|
||||
if (key.Length == 0)
|
||||
return;
|
||||
int key_pos = (int)(offset % key.Length);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
data[index + i] ^= key[key_pos++];
|
||||
if (key.Length == key_pos)
|
||||
key_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override ResourceScheme Scheme
|
||||
@@ -333,9 +335,9 @@ namespace GameRes.Formats.DxLib
|
||||
{
|
||||
public long BaseOffset;
|
||||
public long IndexOffset;
|
||||
public uint IndexSize;
|
||||
public uint FileTable;
|
||||
public uint DirTable;
|
||||
public long IndexSize;
|
||||
public long FileTable;
|
||||
public long DirTable;
|
||||
public int CodePage;
|
||||
}
|
||||
|
||||
@@ -361,8 +363,10 @@ namespace GameRes.Formats.DxLib
|
||||
{
|
||||
if (version <= 4)
|
||||
return new IndexReaderV2 (header, version, input);
|
||||
else if (version >= 6)
|
||||
else if (version >= 6 && version < 8)
|
||||
return new IndexReaderV6 (header, version, input);
|
||||
else if (version >=8)
|
||||
return new IndexReaderV8 (header, version, input);
|
||||
else
|
||||
throw new InvalidFormatException ("Not supported DX archive version.");
|
||||
}
|
||||
@@ -545,7 +549,7 @@ namespace GameRes.Formats.DxLib
|
||||
: base (stream, leave_open)
|
||||
{
|
||||
m_key = key;
|
||||
m_base_pos = (int)(base_position % m_key.Length);
|
||||
m_base_pos = m_key.Length !=0 ?(int)(base_position % m_key.Length):0;
|
||||
}
|
||||
|
||||
public override int Read (byte[] buffer, int offset, int count)
|
||||
@@ -559,32 +563,48 @@ namespace GameRes.Formats.DxLib
|
||||
|
||||
public override int ReadByte ()
|
||||
{
|
||||
int key_pos = (int)((m_base_pos + Position) % m_key.Length);
|
||||
int b = BaseStream.ReadByte();
|
||||
if (-1 != b)
|
||||
if (m_key.Length !=0)
|
||||
{
|
||||
b ^= m_key[key_pos];
|
||||
int key_pos = (int)((m_base_pos + Position) % m_key.Length);
|
||||
if (-1 != b)
|
||||
{
|
||||
b ^= m_key[key_pos];
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
public override void Write (byte[] buffer, int offset, int count)
|
||||
{
|
||||
int key_pos = (int)((m_base_pos + Position) % m_key.Length);
|
||||
|
||||
byte[] write_buf = new byte[count];
|
||||
for (int i = 0; i < count; ++i)
|
||||
if (m_key.Length != 0)
|
||||
{
|
||||
write_buf[i] = (byte)(buffer[offset+i] ^ m_key[key_pos++]);
|
||||
if (m_key.Length == key_pos)
|
||||
key_pos = 0;
|
||||
int key_pos = (int)((m_base_pos + Position) % m_key.Length);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
write_buf[i] = (byte)(buffer[offset + i] ^ m_key[key_pos++]);
|
||||
if (m_key.Length == key_pos)
|
||||
key_pos = 0;
|
||||
}
|
||||
} else
|
||||
{
|
||||
write_buf = buffer;
|
||||
}
|
||||
BaseStream.Write (write_buf, 0, count);
|
||||
}
|
||||
|
||||
public override void WriteByte (byte value)
|
||||
{
|
||||
int key_pos = (int)((m_base_pos + Position) % m_key.Length);
|
||||
BaseStream.WriteByte ((byte)(value ^ m_key[key_pos]));
|
||||
if(m_key.Length != 0)
|
||||
{
|
||||
int key_pos = (int)((m_base_pos + Position) % m_key.Length);
|
||||
BaseStream.WriteByte((byte)(value ^ m_key[key_pos]));
|
||||
} else
|
||||
{
|
||||
BaseStream.WriteByte ((byte)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,47 @@
|
||||
// IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using GameRes.Formats.Strings;
|
||||
using System;
|
||||
using System.CodeDom;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using System.Windows.Navigation;
|
||||
using static GameRes.Formats.DxLib.Dx8Opener;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace GameRes.Formats.DxLib
|
||||
{
|
||||
|
||||
internal class DXA8PackedEntry : PackedEntry {
|
||||
public bool HuffmanCompressed { get; set; }
|
||||
public uint HuffmanSize { get; set; }
|
||||
|
||||
public uint LZSize { get; set; }
|
||||
}
|
||||
|
||||
internal class DxArchive8 : DxArchive
|
||||
{
|
||||
public byte huffmanMaxKB;
|
||||
|
||||
public DxArchive8(ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, IDxKey enc, int version,byte huffmanKB) : base(arc, impl, dir, enc, version)
|
||||
{
|
||||
huffmanMaxKB = huffmanKB;
|
||||
}
|
||||
}
|
||||
|
||||
internal class DxHeaderV8 : DxHeader
|
||||
{
|
||||
public DXA8Flags Flags;
|
||||
public byte HuffmanKB; // oddly used only in Compression process not in decompression.
|
||||
//15 bytes of padding.
|
||||
}
|
||||
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class Dx8Opener : DxOpener
|
||||
{
|
||||
@@ -41,30 +75,252 @@ namespace GameRes.Formats.DxLib
|
||||
|
||||
public Dx8Opener ()
|
||||
{
|
||||
Extensions = new[] { "bin" };
|
||||
Extensions = new string[] { "dxa", "hud", "usi", "med", "dat", "bin", "bcx", "wolf" };
|
||||
Signatures = new[] { 0x00085844u };
|
||||
}
|
||||
|
||||
static readonly byte[] DefaultKey = new byte[] { 0xBE, 0xC8, 0x8A, 0xF5, 0x28, 0x50, 0xC9 };
|
||||
//static readonly byte[] DefaultKey = new byte[] { 0xBE, 0xC8, 0x8A, 0xF5, 0x28, 0x50, 0xC9 };
|
||||
|
||||
|
||||
DxScheme DefaultScheme = new DxScheme { KnownKeys = new List<IDxKey>() };
|
||||
|
||||
|
||||
|
||||
|
||||
internal enum DXA8Flags : UInt32
|
||||
{
|
||||
DXA_FLAG_NO_KEY=1, //file is not encrypted
|
||||
DXA_FLAG_NO_HEAD_PRESS=1<<1, //do not compress the header after compressing individual entries
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class DXAOpts : ResourceOptions
|
||||
{
|
||||
public string Keyword;
|
||||
}
|
||||
|
||||
public override ResourceOptions GetDefaultOptions()
|
||||
{
|
||||
return new DXAOpts
|
||||
{
|
||||
Keyword = Properties.Settings.Default.DXAPassword
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public override ResourceOptions GetOptions(object widget)
|
||||
{
|
||||
if (widget is GUI.WidgetDXA)
|
||||
{
|
||||
return new DXAOpts
|
||||
{
|
||||
Keyword = ((GUI.WidgetDXA)widget).Password.Text
|
||||
};
|
||||
}
|
||||
return GetDefaultOptions();
|
||||
}
|
||||
|
||||
public override object GetAccessWidget()
|
||||
{
|
||||
return new GUI.WidgetDXA();
|
||||
}
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
var dx = new DxHeader {
|
||||
var dx = new DxHeaderV8 {
|
||||
IndexSize = file.View.ReadUInt32 (4),
|
||||
BaseOffset = file.View.ReadInt64 (8),
|
||||
IndexOffset = file.View.ReadInt64 (0x10),
|
||||
FileTable = (uint)file.View.ReadInt64 (0x18),
|
||||
DirTable = (uint)file.View.ReadInt64 (0x20),
|
||||
FileTable = file.View.ReadInt64 (0x18),
|
||||
DirTable = file.View.ReadInt64 (0x20),
|
||||
CodePage = file.View.ReadInt32 (0x28),
|
||||
Flags = (DXA8Flags)file.View.ReadUInt32(0x2C),
|
||||
HuffmanKB = file.View.ReadByte(0x30)
|
||||
};
|
||||
if (dx.DirTable >= dx.IndexSize || dx.FileTable >= dx.IndexSize)
|
||||
return null;
|
||||
var key = DefaultKey;
|
||||
var index = file.View.ReadBytes (dx.IndexOffset, dx.IndexSize);
|
||||
Decrypt (index, 0, index.Length, 0, key);
|
||||
// decrypt-2
|
||||
// decompress
|
||||
return null;
|
||||
DxKey8 key = null;
|
||||
|
||||
//FIXME: ReadBytes sets hard cap of filesize to 4GB.
|
||||
var headerBuffer = file.View.ReadBytes(dx.IndexOffset, (uint)(file.MaxOffset-dx.IndexOffset));
|
||||
bool isencrypted = (dx.Flags & DXA8Flags.DXA_FLAG_NO_KEY) == 0;
|
||||
|
||||
if (isencrypted)
|
||||
{
|
||||
var keyStr = Query<DXAOpts>(arcStrings.ZIPEncryptedNotice).Keyword;
|
||||
key = new DxKey8(keyStr,dx.CodePage);
|
||||
|
||||
|
||||
}
|
||||
|
||||
Decrypt(headerBuffer, 0, headerBuffer.Length, 0, key.Key);
|
||||
//Decrypted but might be compressed
|
||||
if ((dx.Flags & DXA8Flags.DXA_FLAG_NO_HEAD_PRESS) == 0)
|
||||
{
|
||||
byte[] huffmanBuffer = new byte[headerBuffer.Length];
|
||||
byte[] lzBuffer;
|
||||
headerBuffer.CopyTo(huffmanBuffer, 0);
|
||||
//huffmanBuffer = headerBuffer;
|
||||
HuffmanDecoder decoder = new HuffmanDecoder(huffmanBuffer, (ulong)huffmanBuffer.LongLength);
|
||||
lzBuffer = decoder.Unpack();
|
||||
MemoryStream lzStream = new MemoryStream(lzBuffer);
|
||||
headerBuffer = Unpack(lzStream);
|
||||
|
||||
}
|
||||
|
||||
|
||||
List<Entry> entries;
|
||||
//There MAY be the case where the singular file is over 4GB, but it's very rare.
|
||||
using (var reader = IndexReader.Create(dx, 8, new MemoryStream(headerBuffer)))
|
||||
{
|
||||
entries = reader.Read();
|
||||
}
|
||||
return new DxArchive8(file, this,entries ,key, 8,dx.HuffmanKB);
|
||||
//retu rn null;
|
||||
}
|
||||
|
||||
public override Stream OpenEntry(ArcFile arc, Entry entry)
|
||||
{
|
||||
Stream input = arc.File.CreateStream(entry.Offset, entry.Size);
|
||||
var dx_arc = arc as DxArchive8;
|
||||
if (null == dx_arc)
|
||||
return input;
|
||||
var dx_ent = (DXA8PackedEntry)entry;
|
||||
long dec_offset = dx_ent.UnpackedSize;
|
||||
var key = dx_arc.Encryption.GetEntryKey(dx_ent.Name);
|
||||
input = new EncryptedStream(input, dec_offset, key);
|
||||
|
||||
byte[] tmpBuffer = new byte[dx_ent.Size];
|
||||
input.Read(tmpBuffer, 0, tmpBuffer.Length);
|
||||
if (dx_ent.HuffmanCompressed)
|
||||
{
|
||||
byte[] buffer = new byte[dx_ent.HuffmanSize];
|
||||
byte[] outBuffer = new byte[dx_ent.IsPacked ? dx_ent.LZSize : dx_ent.UnpackedSize];
|
||||
Array.Copy(tmpBuffer, buffer, dx_ent.HuffmanSize);
|
||||
HuffmanDecoder decoder = new HuffmanDecoder(buffer,dx_ent.HuffmanSize);
|
||||
byte[] partTmpBuffer = decoder.Unpack();
|
||||
//returned buffer might be partial. Check if this is the case.
|
||||
var outBufSize = dx_ent.IsPacked ? dx_ent.LZSize : dx_ent.UnpackedSize;
|
||||
if(dx_arc.huffmanMaxKB != 0xff && outBufSize > dx_arc.huffmanMaxKB * 1024 * 2)
|
||||
{
|
||||
//What we have here is two huffmanMaxKB KB buffers, that constitute the beginning and end of file respectively.
|
||||
Array.Copy(partTmpBuffer,0, outBuffer, 0,dx_arc.huffmanMaxKB*1024);
|
||||
Array.Copy(partTmpBuffer,dx_arc.huffmanMaxKB*1024,outBuffer,outBuffer.Length-dx_arc.huffmanMaxKB*1024,dx_arc.huffmanMaxKB*1024);
|
||||
//uncompressed part goes into middle.
|
||||
Array.Copy(tmpBuffer, dx_ent.HuffmanSize, outBuffer, dx_arc.huffmanMaxKB * 1024, outBufSize - dx_arc.huffmanMaxKB * 1024 * 2);
|
||||
tmpBuffer = outBuffer;
|
||||
} else
|
||||
{
|
||||
//that is all that needs to be done.
|
||||
tmpBuffer = partTmpBuffer;
|
||||
}
|
||||
}
|
||||
if(dx_ent.IsPacked)
|
||||
{
|
||||
byte[] buffer = new byte[dx_ent.LZSize];
|
||||
tmpBuffer.CopyTo(buffer, 0);
|
||||
var tmpMemStream = new MemoryStream(buffer);
|
||||
tmpBuffer = Unpack(tmpMemStream);
|
||||
|
||||
}
|
||||
return new BinMemoryStream(tmpBuffer, entry.Name);
|
||||
|
||||
/*
|
||||
if (!dx_ent.IsPacked)
|
||||
return input;
|
||||
using (input)
|
||||
{
|
||||
var data = Unpack(input);
|
||||
return new BinMemoryStream(data, entry.Name);
|
||||
}
|
||||
*/
|
||||
//return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class IndexReaderV8 : IndexReader
|
||||
{
|
||||
readonly int m_entry_size;
|
||||
public IndexReaderV8(DxHeader header, int version, Stream input) : base(header, version, input)
|
||||
{
|
||||
m_entry_size = 0x48;
|
||||
}
|
||||
private class DxDirectory
|
||||
{
|
||||
public long DirOffset;
|
||||
public long ParentDirOffset;
|
||||
public int FileCount;
|
||||
public long FileTable;
|
||||
}
|
||||
|
||||
DxDirectory ReadDirEntry()
|
||||
{
|
||||
var dir = new DxDirectory
|
||||
{
|
||||
DirOffset = m_input.ReadInt64(),
|
||||
ParentDirOffset = m_input.ReadInt64(),
|
||||
FileCount = (int)m_input.ReadInt64(),
|
||||
FileTable = m_input.ReadInt64()
|
||||
};
|
||||
return dir;
|
||||
}
|
||||
|
||||
protected override void ReadFileTable(string root, long table_offset)
|
||||
{
|
||||
m_input.Position = m_header.DirTable + table_offset;
|
||||
var dir = ReadDirEntry();
|
||||
if (dir.DirOffset != -1 && dir.ParentDirOffset != -1)
|
||||
{
|
||||
m_input.Position = m_header.FileTable + dir.DirOffset;
|
||||
root = Path.Combine(root, ExtractFileName(m_input.ReadInt64()));
|
||||
}
|
||||
long current_pos = m_header.FileTable + dir.FileTable;
|
||||
for (int i = 0; i < dir.FileCount; ++i)
|
||||
{
|
||||
m_input.Position = current_pos;
|
||||
var name_offset = m_input.ReadInt64();
|
||||
uint attr = (uint)m_input.ReadInt64();
|
||||
m_input.Seek(0x18, SeekOrigin.Current);
|
||||
var offset = m_input.ReadInt64();
|
||||
if (0 != (attr & 0x10)) // FILE_ATTRIBUTE_DIRECTORY
|
||||
{
|
||||
if (0 == offset || table_offset == offset)
|
||||
throw new InvalidFormatException("Infinite recursion in DXA directory index");
|
||||
ReadFileTable(root, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
var size = m_input.ReadInt64();
|
||||
var packed_size = m_input.ReadInt64();
|
||||
var huffman_packed_size = m_input.ReadInt64();
|
||||
var entry = FormatCatalog.Instance.Create<DXA8PackedEntry>(Path.Combine(root, ExtractFileName(name_offset)));
|
||||
entry.Offset = m_header.BaseOffset + offset;
|
||||
entry.UnpackedSize = (uint)size;
|
||||
entry.IsPacked = -1 != packed_size;
|
||||
entry.HuffmanCompressed = -1 != huffman_packed_size;
|
||||
entry.HuffmanSize = (uint)huffman_packed_size;
|
||||
entry.LZSize = (uint)packed_size;
|
||||
//Huffman compression: huffman_packed_size will not exceed 2*HuffmanKB KB. The rest of data is uncompressed (as far as Huffman compressor is concerned).
|
||||
//Add length of uncompressed data to entry.Size
|
||||
if (entry.HuffmanCompressed)
|
||||
{
|
||||
var outBufSize = entry.IsPacked ? packed_size : size;
|
||||
var dx8_hdr = (DxHeaderV8)m_header;
|
||||
if (outBufSize > dx8_hdr.HuffmanKB * 1024 * 2)
|
||||
{
|
||||
huffman_packed_size += outBufSize - dx8_hdr.HuffmanKB * 1024 * 2;
|
||||
}
|
||||
}
|
||||
if (entry.IsPacked||entry.HuffmanCompressed)
|
||||
entry.Size = (uint)(huffman_packed_size!=-1 ? huffman_packed_size:packed_size);
|
||||
else
|
||||
entry.Size = (uint)size;
|
||||
m_dir.Add(entry);
|
||||
}
|
||||
current_pos += m_entry_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.DxLib
|
||||
@@ -153,4 +155,78 @@ namespace GameRes.Formats.DxLib
|
||||
throw new NotSupportedException ("SHA-256 key cannot be restored.");
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class DxKey8 : DxKey
|
||||
{
|
||||
private int codepage;
|
||||
public DxKey8(string password,int codepage) : base(password ?? "DXARC")
|
||||
{
|
||||
this.codepage = codepage;
|
||||
}
|
||||
|
||||
public override byte[] GetEntryKey(string name)
|
||||
{
|
||||
var password = this.Password;
|
||||
var path = name.Split('\\', '/');
|
||||
password += string.Join("", path.Reverse().Select(n => n.ToUpperInvariant()));
|
||||
return CreateKey(password);
|
||||
}
|
||||
|
||||
protected override byte[] CreateKey(string keyword)
|
||||
{
|
||||
//from DxArchive.cpp
|
||||
//first check if the keyword is too short
|
||||
if (keyword.Length < 4)
|
||||
{
|
||||
keyword += "DXARC";
|
||||
}
|
||||
//first split string to bytes. Use original encoding as basis. Otherwise we would fail to decrypt that.
|
||||
Encoding tgtEncoding = Encoding.GetEncoding(codepage);
|
||||
byte[] tgtBytes = tgtEncoding.GetBytes(keyword);
|
||||
byte[] oddBuffer = new byte[(tgtBytes.Length/2)+(tgtBytes.Length%2)]; int oddCounter = 0;
|
||||
byte[] evenBuffer = new byte[(tgtBytes.Length/2)]; int evenCounter = 0;
|
||||
for (int i=0; i<tgtBytes.Length;i+=2,oddCounter++)
|
||||
{
|
||||
oddBuffer[oddCounter] = tgtBytes[i];
|
||||
}
|
||||
for (int i = 1; i < tgtBytes.Length; i += 2, evenCounter++)
|
||||
{
|
||||
evenBuffer[evenCounter] = tgtBytes[i];
|
||||
}
|
||||
UInt32 crc_0, crc_1;
|
||||
crc_0 = Crc32.Compute(oddBuffer, 0, oddCounter);
|
||||
crc_1 = Crc32.Compute(evenBuffer, 0, evenCounter);
|
||||
|
||||
byte[] key = new byte[7];
|
||||
byte[] crc_0_Bytes = BitConverter.GetBytes(crc_0),crc_1_Bytes=BitConverter.GetBytes(crc_1);
|
||||
key[0] = crc_0_Bytes[0];
|
||||
key[1] = crc_0_Bytes[1];
|
||||
key[2] = crc_0_Bytes[2];
|
||||
key[3] = crc_0_Bytes[3];
|
||||
key[4] = crc_1_Bytes[0];
|
||||
key[5] = crc_1_Bytes[1];
|
||||
key[6] = crc_1_Bytes[2];
|
||||
return key;
|
||||
|
||||
/*
|
||||
string oddString, evenString;
|
||||
oddString = string.Concat(keyword.Where((c, i) => i % 2 == 0));
|
||||
evenString = string.Concat(keyword.Where((c, i) => (i+1) % 2 == 0));
|
||||
UInt32 crc_0, crc_1;
|
||||
crc_0 = Crc32.Compute(Encoding.ASCII.GetBytes(oddString), 0, oddString.Length);
|
||||
crc_1 = Crc32.Compute(Encoding.ASCII.GetBytes(evenString), 0, evenString.Length); */
|
||||
/*
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
var bytes = Encodings.cp932.GetBytes(keyword);
|
||||
return sha.ComputeHash(bytes);
|
||||
} */
|
||||
}
|
||||
|
||||
protected override string RestoreKey(byte[] key)
|
||||
{
|
||||
throw new NotSupportedException("CRC key cannot be restored.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
368
ArcFormats/DxLib/HuffmanDecoder.cs
Normal file
368
ArcFormats/DxLib/HuffmanDecoder.cs
Normal file
@@ -0,0 +1,368 @@
|
||||
//! \file HuffmanDecoder.cs
|
||||
//! \date 2024 Aug 2
|
||||
//! \brief Custom Huffman decoder for DXA archives.
|
||||
//
|
||||
// Copyright (C) 2017 by morkt - GetBits function
|
||||
// Copyright (C) 2024 by MrSoup678
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
//Original file is Huffman.cpp. Creator: 山田 巧 Date: 2018 Dec 16
|
||||
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.DxLib
|
||||
{
|
||||
internal class DXA8HuffmanNode
|
||||
{
|
||||
public UInt64 Weight;
|
||||
public int bitNumber;
|
||||
public byte[] bitArray; //32 bytes here
|
||||
public int Index;
|
||||
|
||||
public int ParentNode; // index of parent node.
|
||||
public int[] ChildNode; //two children nodes, -1 if not existent.
|
||||
|
||||
internal DXA8HuffmanNode()
|
||||
{
|
||||
bitArray = new byte[32];
|
||||
ChildNode = new int[2];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class HuffmanDecoder
|
||||
{
|
||||
byte[] m_input;
|
||||
byte[] m_output;
|
||||
|
||||
int m_src;
|
||||
byte m_bits;
|
||||
int m_bit_count;
|
||||
|
||||
ulong m_readBytes;
|
||||
byte m_readBits;
|
||||
|
||||
|
||||
DXA8HuffmanNode[] nodes; //256+255 nodes
|
||||
|
||||
ulong originalSize;
|
||||
ulong compressedSize;
|
||||
ulong headerSize;
|
||||
|
||||
ulong srcSize;
|
||||
|
||||
//ushort token = 256;
|
||||
|
||||
public HuffmanDecoder (byte[] src,ulong srcSize)
|
||||
{
|
||||
m_input = src;
|
||||
m_output = null;
|
||||
|
||||
this.srcSize = srcSize;
|
||||
m_src = 0;
|
||||
m_bit_count = 0;
|
||||
m_readBytes = 0;
|
||||
m_readBits = 0;
|
||||
originalSize = compressedSize = headerSize = 0;
|
||||
ushort[] weights = new ushort[256];
|
||||
nodes = new DXA8HuffmanNode[256+255]; //256 data nodes, then 255 hierarchy nodes.
|
||||
for (int i = 0; i < nodes.Length; i++)
|
||||
{
|
||||
nodes[i] = new DXA8HuffmanNode();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Unpack ()
|
||||
{
|
||||
|
||||
for (int i=0; i<nodes.Length; i++)
|
||||
{
|
||||
nodes[i].ParentNode = -1;
|
||||
nodes[i].ChildNode[0] = -1;
|
||||
nodes[i].ChildNode[1] = -1;
|
||||
}
|
||||
SetupWeights();
|
||||
//check if compressedSize and src size match.
|
||||
if (srcSize!=(compressedSize+headerSize))
|
||||
{
|
||||
throw new FileSizeException(String.Format("Supplied srcSize does not match with compressedSize+headerSize. Expected {0} got {1}",compressedSize+headerSize,srcSize));
|
||||
}
|
||||
m_output = new byte[originalSize];
|
||||
CreateTree();
|
||||
PopulateDataNodes();
|
||||
DoUnpack();
|
||||
return m_output;
|
||||
}
|
||||
|
||||
private void DoUnpack()
|
||||
{
|
||||
var targetSize = originalSize;
|
||||
byte[] compressedData = new byte[compressedSize];
|
||||
Array.Copy(m_input, (long)headerSize, compressedData, 0, (long)(compressedSize));
|
||||
|
||||
int PressBitCounter=0, PressBitData=0, Index=0, NodeIndex=0;
|
||||
int PressSizeCounter = 0;
|
||||
ulong DestSizeCounter = 0;
|
||||
int[] NodeIndexTable=new int[512];
|
||||
{
|
||||
ushort[] bitMask = new ushort[9];
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
bitMask[i] = (ushort)((1<<i+1) - 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 512; i++)
|
||||
{
|
||||
NodeIndexTable[i] = -1;
|
||||
|
||||
for (int j = 0; j < 256 + 254; j++)
|
||||
{
|
||||
ushort BitArrayFirstBatch;
|
||||
if (nodes[j].bitNumber > 9) continue;
|
||||
|
||||
BitArrayFirstBatch = (ushort)(nodes[j].bitArray[0] | (nodes[j].bitArray[1] << 8));
|
||||
|
||||
if ((i & bitMask[nodes[j].bitNumber - 1]) == (BitArrayFirstBatch & bitMask[nodes[j].bitNumber-1]))
|
||||
{
|
||||
NodeIndexTable[i] = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
PressBitData = compressedData[PressBitCounter];
|
||||
|
||||
for (DestSizeCounter = 0;DestSizeCounter < originalSize; DestSizeCounter++)
|
||||
{
|
||||
if (DestSizeCounter>= originalSize - 17)
|
||||
{
|
||||
NodeIndex = 510;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PressBitCounter==8)
|
||||
{
|
||||
PressSizeCounter++;
|
||||
PressBitData = compressedData[PressSizeCounter];
|
||||
PressBitCounter = 0;
|
||||
}
|
||||
|
||||
PressBitData = (PressBitData | (compressedData[PressSizeCounter+1]<<(8-PressBitCounter))) & 0x1ff;
|
||||
NodeIndex = NodeIndexTable[PressBitData];
|
||||
PressBitCounter += nodes[NodeIndex].bitNumber;
|
||||
if (PressBitCounter >= 16)
|
||||
{
|
||||
PressSizeCounter += 2;
|
||||
PressBitCounter -= 16;
|
||||
PressBitData = compressedData[PressSizeCounter] >> PressBitCounter;
|
||||
}
|
||||
else if (PressBitCounter >=8)
|
||||
{
|
||||
PressSizeCounter ++;
|
||||
PressBitCounter -= 8;
|
||||
PressBitData = compressedData[PressSizeCounter] >> PressBitCounter;
|
||||
}
|
||||
else
|
||||
{
|
||||
PressBitData >>= nodes[NodeIndex].bitNumber;
|
||||
}
|
||||
}
|
||||
|
||||
while (NodeIndex > 255)
|
||||
{
|
||||
if (PressBitCounter == 8)
|
||||
{
|
||||
PressSizeCounter++;
|
||||
PressBitData = compressedData[PressSizeCounter];
|
||||
PressBitCounter = 0;
|
||||
}
|
||||
Index = PressBitData & 1;
|
||||
PressBitData >>= 1;
|
||||
PressBitCounter++;
|
||||
NodeIndex = nodes[NodeIndex].ChildNode[Index];
|
||||
}
|
||||
m_output[DestSizeCounter] = (byte)NodeIndex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void PopulateDataNodes()
|
||||
{
|
||||
//The data which is populated is path from root to target node in bits.
|
||||
byte[] ScratchSpace = new byte[32];
|
||||
int TempBitIndex, TempBitCount;
|
||||
|
||||
for (int i = 0; i < 256 + 254; i++) //root node is excluded.
|
||||
{
|
||||
nodes[i].bitNumber = 0;
|
||||
TempBitIndex = 0;
|
||||
TempBitCount = 0;
|
||||
ScratchSpace[TempBitIndex] = 0;
|
||||
|
||||
for (int j = i; nodes[j].ParentNode!=-1;j = nodes[j].ParentNode)
|
||||
{
|
||||
if (TempBitCount == 8)
|
||||
{
|
||||
TempBitCount = 0;
|
||||
TempBitIndex++;
|
||||
ScratchSpace[TempBitIndex] = 0;
|
||||
}
|
||||
ScratchSpace[TempBitIndex] <<= 1;
|
||||
ScratchSpace[TempBitIndex] |= (byte)nodes[j].Index;
|
||||
TempBitCount++;
|
||||
nodes[i].bitNumber++;
|
||||
|
||||
}
|
||||
//path is now backwards (target to root). Populate BitPath from root to target.
|
||||
int BitIndex=0, BitCount=0;
|
||||
nodes[i].bitArray[BitIndex] = 0;
|
||||
while (TempBitIndex >= 0)
|
||||
{
|
||||
if (BitCount == 8)
|
||||
{
|
||||
BitCount = 0;
|
||||
BitIndex++;
|
||||
nodes[i].bitArray[BitIndex] = 0;
|
||||
}
|
||||
nodes[i].bitArray[BitIndex] |= (byte)((ScratchSpace[TempBitIndex] & 1) << BitCount);
|
||||
ScratchSpace[TempBitIndex] >>= 1;
|
||||
TempBitCount--;
|
||||
if (TempBitCount == 0)
|
||||
{
|
||||
TempBitIndex--;
|
||||
TempBitCount = 8;
|
||||
}
|
||||
BitCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void SetupWeights()
|
||||
{
|
||||
int sizeA, sizeB;
|
||||
byte BitNum;
|
||||
byte Minus;
|
||||
ushort SaveData;
|
||||
ushort[] weights = new ushort[256];
|
||||
sizeA = (int)GetBits(6) + 1;
|
||||
originalSize = GetBits(sizeA);
|
||||
sizeB = (int)GetBits(6)+1;
|
||||
compressedSize = GetBits(sizeB);
|
||||
|
||||
BitNum = (byte)(((int)GetBits(3) + 1) * 2);
|
||||
Minus = (byte)GetBits(1);
|
||||
SaveData = (ushort)GetBits(BitNum);
|
||||
weights[0] = SaveData;
|
||||
for (int i = 1; i < 256; i++)
|
||||
{
|
||||
BitNum = (byte)(((int)GetBits(3) + 1) * 2);
|
||||
Minus = (byte)GetBits(1);
|
||||
SaveData = (ushort)GetBits(BitNum);
|
||||
weights[i] = (ushort)(Minus == 1 ? weights[i - 1] - SaveData : weights[i - 1] + SaveData);
|
||||
}
|
||||
headerSize = GetReadBytes();
|
||||
for (int i = 0;i < 256; i++)
|
||||
{
|
||||
nodes[i].Weight = weights[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CreateTree()
|
||||
{
|
||||
int NodeNum=256, DataNum=256;
|
||||
|
||||
while (DataNum > 1)
|
||||
{
|
||||
int MinNode1 = -1;
|
||||
int MinNode2 = -1;
|
||||
int NodeIndex = 0;
|
||||
|
||||
|
||||
for (int i = 0; i < DataNum; NodeIndex++) {
|
||||
//don't do anything if we already have a parent set.
|
||||
if (nodes[NodeIndex].ParentNode != -1) continue;
|
||||
i++;
|
||||
//we need to get the two lowest numbers for parenting.
|
||||
if (MinNode1 == -1 || nodes[MinNode1].Weight > nodes[NodeIndex].Weight)
|
||||
{
|
||||
{
|
||||
MinNode2 = MinNode1;
|
||||
MinNode1 = NodeIndex;
|
||||
}
|
||||
} else if (MinNode2 == -1 || nodes[MinNode2].Weight > nodes[NodeIndex].Weight)
|
||||
{
|
||||
MinNode2 = NodeIndex;
|
||||
}
|
||||
}
|
||||
nodes[NodeNum].ParentNode = -1;
|
||||
nodes[NodeNum].Weight = nodes[MinNode1].Weight + nodes[MinNode2].Weight;
|
||||
nodes[NodeNum].ChildNode[0] = MinNode1;
|
||||
nodes[NodeNum].ChildNode[1] = MinNode2;
|
||||
nodes[MinNode1].Index = 0;
|
||||
nodes[MinNode2].Index = 1;
|
||||
nodes[MinNode1].ParentNode = NodeNum;
|
||||
nodes[MinNode2].ParentNode = NodeNum;
|
||||
|
||||
NodeNum++;
|
||||
DataNum--;
|
||||
}
|
||||
}
|
||||
|
||||
ulong GetBits (int count)
|
||||
{
|
||||
ulong bits = 0;
|
||||
for (int i = 0; i < count;i++)
|
||||
{
|
||||
if (0 == m_bit_count)
|
||||
{
|
||||
m_bits = m_input[m_src];
|
||||
m_src++;
|
||||
m_bit_count = 8;
|
||||
}
|
||||
//bits are read backwards.
|
||||
bits |= ((ulong)((m_bits >> (7 - m_readBits)) & 1)) <<(count-1-i);
|
||||
--m_bit_count;
|
||||
m_readBits++;
|
||||
if (m_readBits ==8)
|
||||
{
|
||||
m_readBits = 0;
|
||||
m_readBytes++;
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
ulong GetReadBytes()
|
||||
{
|
||||
return m_readBytes + (m_readBits != 0 ? 1ul : 0ul);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
ArcFormats/DxLib/WidgetDXA.xaml
Normal file
19
ArcFormats/DxLib/WidgetDXA.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<StackPanel x:Class="GameRes.Formats.GUI.WidgetDXA"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:GameRes.Formats.DxLib"
|
||||
mc:Ignorable="d"
|
||||
|
||||
xmlns:s="clr-namespace:GameRes.Formats.Strings"
|
||||
xmlns:p="clr-namespace:GameRes.Formats.Properties"
|
||||
xmlns:dxa="clr-namespace:GameRes.Formats.DxLib">
|
||||
<Label Content="{x:Static s:arcStrings.ArcTitleOrPassword}" HorizontalAlignment="Left"/>
|
||||
<ComboBox Name="Title" Width="200" HorizontalAlignment="Left"
|
||||
ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=DataContext}"
|
||||
SelectedValue="{Binding ElementName=Password, Path=Text, Mode=TwoWay}" SelectedValuePath="Value"
|
||||
DisplayMemberPath="Key"/>
|
||||
<TextBox x:Name="Password" Width="200" HorizontalAlignment="Left" Margin="0,5,0,0"
|
||||
Text="{Binding Source={x:Static p:Settings.Default}, Path=DXAPassword, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
29
ArcFormats/DxLib/WidgetDXA.xaml.cs
Normal file
29
ArcFormats/DxLib/WidgetDXA.xaml.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using GameRes.Formats.DxLib;
|
||||
|
||||
namespace GameRes.Formats.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Logika interakcji dla klasy WidgetDXA.xaml
|
||||
/// </summary>
|
||||
public partial class WidgetDXA : StackPanel
|
||||
{
|
||||
public WidgetDXA()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,16 @@ namespace GameRes.Formats.BGI
|
||||
return null;
|
||||
if (0 != stream.ReadInt64())
|
||||
return null;
|
||||
|
||||
if (flag == 0)
|
||||
{
|
||||
int stride = (int)width * ((bpp + 7) / 8);
|
||||
var pixels = new byte[stride * (int)height];
|
||||
int read = stream.Read(pixels, 0, pixels.Length);
|
||||
if (read != pixels.Length)
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BgiMetaData
|
||||
{
|
||||
Width = (uint)width,
|
||||
|
||||
@@ -27,8 +27,10 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using GameRes.Utility;
|
||||
using GameRes.Compression;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace GameRes.Formats.Eushully
|
||||
{
|
||||
@@ -63,15 +65,64 @@ namespace GameRes.Formats.Eushully
|
||||
return null;
|
||||
}
|
||||
|
||||
static internal string GetAAIName(string alf_name)
|
||||
{
|
||||
const string pattern = @"^(APPEND(?:[0-9]+)?)(?:_[0-9]+)?\.ALF$";
|
||||
var match = Regex.Match(alf_name, pattern);
|
||||
if (match.Success)
|
||||
return match.Groups[1].Value;
|
||||
return alf_name;
|
||||
}
|
||||
|
||||
internal IEnumerable<string> GetIndexNames (string alf_name)
|
||||
{
|
||||
yield return "sys5ini.bin";
|
||||
yield return "sys4ini.bin";
|
||||
yield return "sys3ini.bin";
|
||||
yield return Path.ChangeExtension (alf_name, "AAI");
|
||||
yield return Path.ChangeExtension (GetAAIName(alf_name), "AAI");
|
||||
}
|
||||
|
||||
Tuple<string, Dictionary<string, List<Entry>>> LastAccessedIndex;
|
||||
|
||||
|
||||
internal class AGEArchiveInfo
|
||||
{
|
||||
public readonly byte[] signature;
|
||||
public readonly int offset;
|
||||
public readonly bool isNameUnicode;
|
||||
public readonly bool isLZSSCompressed;
|
||||
|
||||
public AGEArchiveInfo(byte[] Signature, int Offset, bool IsNameUnicode, bool IsLZSSCompressed)
|
||||
{
|
||||
signature = Signature;
|
||||
offset = Offset;
|
||||
isNameUnicode = IsNameUnicode;
|
||||
isLZSSCompressed = IsLZSSCompressed;
|
||||
}
|
||||
}
|
||||
|
||||
static AGEArchiveInfo[] infos =
|
||||
{
|
||||
new AGEArchiveInfo(Encoding.ASCII.GetBytes("S3IN"), 0x12C, false, false),
|
||||
new AGEArchiveInfo(Encoding.ASCII.GetBytes("S3IC"), 0x134, false, true),
|
||||
new AGEArchiveInfo(Encoding.ASCII.GetBytes("S3AC"), 0x114, false, true),
|
||||
new AGEArchiveInfo(Encoding.ASCII.GetBytes("S4IC"), 0x134, false, true),
|
||||
new AGEArchiveInfo(Encoding.ASCII.GetBytes("S4AC"), 0x114, false, true),
|
||||
new AGEArchiveInfo(Encoding.Unicode.GetBytes("S5IC"), 0x224, true, true),
|
||||
new AGEArchiveInfo(Encoding.Unicode.GetBytes("S5AC"), 0x21C, true, true)
|
||||
};
|
||||
|
||||
static internal AGEArchiveInfo GetAGEArcInfo(ArcView view)
|
||||
{
|
||||
byte[] sig = view.View.ReadBytes(0, 8);
|
||||
var siglow = sig.Take(4);
|
||||
var res = infos.Where(i => Enumerable.SequenceEqual(i.signature, siglow));
|
||||
if (res.Any()) return res.First();
|
||||
res = infos.Where(i => Enumerable.SequenceEqual(i.signature, sig));
|
||||
if (res.Any()) return res.First();
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Entry> ReadIndex (string ini_file, string arc_name)
|
||||
{
|
||||
if (null == LastAccessedIndex
|
||||
@@ -81,24 +132,21 @@ namespace GameRes.Formats.Eushully
|
||||
using (var ini = VFS.OpenView (ini_file))
|
||||
{
|
||||
IBinaryStream index;
|
||||
bool is_append = ini.View.AsciiEqual (0, "S4AC");
|
||||
if (is_append || ini.View.AsciiEqual (0, "S4IC") || ini.View.AsciiEqual (0, "S3IC"))
|
||||
|
||||
AGEArchiveInfo info = GetAGEArcInfo (ini);
|
||||
if (info == null) return null;
|
||||
|
||||
if (info.isLZSSCompressed)
|
||||
{
|
||||
uint offset = is_append ? 0x114u : 0x134u;
|
||||
uint packed_size = ini.View.ReadUInt32 (offset);
|
||||
var packed = ini.CreateStream (offset+4, packed_size);
|
||||
var unpacked = new LzssStream (packed);
|
||||
index = new BinaryStream (unpacked, ini_file);
|
||||
}
|
||||
else if (ini.View.AsciiEqual (0, "S3IN"))
|
||||
{
|
||||
index = ini.CreateStream (0x12C);
|
||||
index = new BinaryStream(new LzssStream(ini.CreateStream(info.offset + 4, (uint)ini.View.ReadInt32(info.offset))), ini_file);
|
||||
}
|
||||
else
|
||||
return null;
|
||||
{
|
||||
index = ini.CreateStream(info.offset);
|
||||
}
|
||||
using (index)
|
||||
{
|
||||
var file_table = ReadSysIni (index);
|
||||
var file_table = ReadSysIni (index, info);
|
||||
if (null == file_table)
|
||||
return null;
|
||||
LastAccessedIndex = Tuple.Create (ini_file, file_table);
|
||||
@@ -110,7 +158,7 @@ namespace GameRes.Formats.Eushully
|
||||
return dir;
|
||||
}
|
||||
|
||||
internal Dictionary<string, List<Entry>> ReadSysIni (IBinaryStream index)
|
||||
internal Dictionary<string, List<Entry>> ReadSysIni (IBinaryStream index, AGEArchiveInfo info)
|
||||
{
|
||||
int arc_count = index.ReadInt32();
|
||||
if (!IsSaneCount (arc_count))
|
||||
@@ -119,7 +167,8 @@ namespace GameRes.Formats.Eushully
|
||||
var arc_list = new List<Entry>[arc_count];
|
||||
for (int i = 0; i < arc_count; ++i)
|
||||
{
|
||||
var name = index.ReadCString (0x100);
|
||||
string name = info.isNameUnicode ? index.ReadCString(0x200, Encoding.Unicode) : index.ReadCString(0x100);
|
||||
|
||||
var file_list = new List<Entry>();
|
||||
file_table.Add (name, file_list);
|
||||
arc_list[i] = file_list;
|
||||
@@ -127,9 +176,10 @@ namespace GameRes.Formats.Eushully
|
||||
int file_count = index.ReadInt32();
|
||||
if (!IsSaneCount (file_count))
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < file_count; ++i)
|
||||
{
|
||||
var name = index.ReadCString (0x40);
|
||||
string name = info.isNameUnicode ? index.ReadCString(0x80, Encoding.Unicode) : index.ReadCString(0x40);
|
||||
int arc_id = index.ReadInt32();
|
||||
if (arc_id < 0 || arc_id >= arc_list.Length)
|
||||
return null;
|
||||
|
||||
50
ArcFormats/FC01/ArcSCXA.cs
Normal file
50
ArcFormats/FC01/ArcSCXA.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
|
||||
namespace GameRes.Formats.FC01
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class SCXAOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "ARC/SCXA Pia Carrot e Youkoso 4 Xbox360"; } }
|
||||
public override string Description { get { return "Pia Carrot e Youkoso 4 Xbox360 resource archive"; } }
|
||||
public override uint Signature { get { return 0x41584353; } } // 'SCXA'
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override ArcFile TryOpen(ArcView file)
|
||||
{
|
||||
uint datastart = file.View.ReadUInt32(4);
|
||||
int count = file.View.ReadInt32(8);
|
||||
if (!IsSaneCount(count))
|
||||
return null;
|
||||
uint tnstart = (uint)count * 4 + 12;
|
||||
var dir = new List<Entry>(count);
|
||||
uint index_offset;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
uint namestart = file.View.ReadUInt32(i * 4 + 12);
|
||||
uint fstart = file.View.ReadUInt32(tnstart + namestart);
|
||||
uint flength = file.View.ReadUInt32(tnstart + namestart + 4);
|
||||
index_offset = tnstart + namestart + 8;
|
||||
byte c;
|
||||
List<byte> namebyte = new List<byte>();
|
||||
while (true)
|
||||
{
|
||||
c = file.View.ReadByte((long)index_offset);
|
||||
if (c == 0 | index_offset > datastart) break;
|
||||
namebyte.Add(c);
|
||||
index_offset++;
|
||||
}
|
||||
var name = System.Text.Encoding.ASCII.GetString(namebyte.ToArray());
|
||||
var entry = Create<Entry>(name);
|
||||
entry.Offset = datastart + fstart;
|
||||
entry.Size = flength;
|
||||
if (!entry.CheckPlacement(file.MaxOffset))
|
||||
return null;
|
||||
dir.Add(entry);
|
||||
}
|
||||
return new ArcFile(file, this, dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,11 @@
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Interheart
|
||||
@@ -37,6 +41,7 @@ namespace GameRes.Formats.Interheart
|
||||
public override string Tag { get { return "KG"; } }
|
||||
public override string Description { get { return "Interheart image format"; } }
|
||||
public override uint Signature { get { return 0x4B474347; } } // 'GCGK'
|
||||
public override bool CanWrite { get { return true; } }
|
||||
|
||||
public override ImageMetaData ReadMetaData (IBinaryStream stream)
|
||||
{
|
||||
@@ -94,9 +99,78 @@ namespace GameRes.Formats.Interheart
|
||||
return ImageData.Create (info, PixelFormats.Bgra32, null, pixels);
|
||||
}
|
||||
|
||||
public override void Write (Stream file, ImageData image)
|
||||
{
|
||||
throw new System.NotImplementedException ("KgFormat.Write not implemented");
|
||||
public override void Write (Stream file, ImageData image) {
|
||||
int stride = (int)image.Width * 4;
|
||||
byte[] bitmap = new byte[stride * image.Height];
|
||||
{
|
||||
var image_bgra = image.Bitmap.Format != PixelFormats.Bgra32
|
||||
? new FormatConvertedBitmap(image.Bitmap, PixelFormats.Bgra32, null, 0)
|
||||
: image.Bitmap;
|
||||
image_bgra.CopyPixels(bitmap, stride, 0);
|
||||
}
|
||||
uint[] offset_table = new uint[image.Height];
|
||||
var kg_data = new List<byte>();
|
||||
|
||||
for (int y = 0; y < image.Height; y++) {
|
||||
offset_table[y] = (uint)kg_data.Count();
|
||||
|
||||
bitmap
|
||||
.AsMemory()
|
||||
.Slice(y * stride, stride)
|
||||
.Chunk(4)
|
||||
.Aggregate(new List<(byte alpha, List<byte[]> data)> { }, (chunks, x) => {
|
||||
var pixel = x.ToArray();
|
||||
void new_chunk () => chunks.Add((alpha: pixel[3], data: new List<byte[]> { pixel }));
|
||||
|
||||
if (!chunks.Any()) {
|
||||
new_chunk();
|
||||
}
|
||||
else {
|
||||
var (alpha, data) = chunks.Last();
|
||||
if (pixel[3] != data.Last()[3] || data.Count >= 256) {
|
||||
new_chunk();
|
||||
}
|
||||
else {
|
||||
data.Add(pixel);
|
||||
}
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
)
|
||||
.ForEach(chunk => {
|
||||
kg_data.Add(chunk.alpha);
|
||||
kg_data.Add((byte)chunk.data.Count());
|
||||
if (chunk.alpha > 0) {
|
||||
kg_data.AddRange(chunk.data.SelectMany(p => new[] { p[2], p[1], p[0] }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var bw = new BinaryWriter(file);
|
||||
|
||||
bw.Write(new byte[] { 0x47, 0x43, 0x47, 0x4B });
|
||||
bw.Write((UInt16)image.Width);
|
||||
bw.Write((UInt16)image.Height);
|
||||
bw.Write((uint)kg_data.Count);
|
||||
|
||||
{
|
||||
byte[] buf = new byte[offset_table.Length * sizeof(uint)];
|
||||
Buffer.BlockCopy(offset_table, 0, buf, 0, buf.Length);
|
||||
bw.Write(buf);
|
||||
}
|
||||
|
||||
bw.Write(kg_data.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
static class Extensions {
|
||||
public static IEnumerable<Memory<T>> Chunk<T>(this Memory<T> arr, int size) {
|
||||
for (var i = 0; i < arr.Length / size + 1; i++) {
|
||||
if (i * size < arr.Length) {
|
||||
yield return arr.Slice(i * size, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace GameRes.Formats.ISM
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
if (!file.View.AsciiEqual (4, "ARCHIVED"))
|
||||
if (!file.View.AsciiEqual (4, "ARCHIVED") && !file.View.AsciiEqual(4, "ENGLISH "))
|
||||
return null;
|
||||
int count = file.View.ReadInt16 (0x0C);
|
||||
if (!IsSaneCount (count))
|
||||
|
||||
@@ -103,6 +103,9 @@ namespace GameRes.Formats.Ivory
|
||||
bool IsValidInput (Stream input, uint width, uint height, int pixel_size)
|
||||
{
|
||||
int total = (int)width * (int)height;
|
||||
// Other formats will be incorrectly recognized as this format, try to correct it.
|
||||
if (total * pixel_size < input.Length / 10)
|
||||
return false;
|
||||
int dst = 0;
|
||||
while (dst < total)
|
||||
{
|
||||
|
||||
150
ArcFormats/Kid/ArcDATRAW.cs
Normal file
150
ArcFormats/Kid/ArcDATRAW.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using GameRes.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
|
||||
namespace GameRes.Formats.Kid
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class DATRAWOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "DAT/KID & MAGES PS2 DAT RAW"; } }
|
||||
public override string Description { get { return "DAT/KID & MAGES PS2 DAT RAW"; } }
|
||||
public override uint Signature { get { return 0; } } //actually zero
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
private static readonly uint dataEntryCount = 4096;
|
||||
|
||||
public override ArcFile TryOpen(ArcView file)
|
||||
{
|
||||
var archivename = Path.GetFileNameWithoutExtension(file.Name);
|
||||
var dir = new List<Entry>();
|
||||
for (int i = 0; i < dataEntryCount; i++)
|
||||
{
|
||||
uint offset = file.View.ReadUInt32(i * 8);
|
||||
uint size = file.View.ReadUInt32(i * 8 + 4);
|
||||
offset = offset * 2048 + 0x8000;
|
||||
size *= 1024;
|
||||
if (offset > file.MaxOffset || size > file.MaxOffset)
|
||||
{
|
||||
throw new InvalidFormatException();
|
||||
}
|
||||
if (size == 0) continue;
|
||||
|
||||
var entry = Create<Entry>(archivename + i.ToString("D5"));
|
||||
entry.Offset = offset;
|
||||
entry.Size = size;
|
||||
dir.Add(entry);
|
||||
}
|
||||
if (dir.Count == 0)
|
||||
return null;
|
||||
return new ArcFile(file, this, dir);
|
||||
}
|
||||
|
||||
/*public override Stream OpenEntry(ArcFile arc, Entry entry)
|
||||
{
|
||||
IBinaryStream input = arc.File.CreateStream(entry.Offset, entry.Size, entry.Name);
|
||||
if (input.Signature == 0x535043) // 'CPS'
|
||||
{
|
||||
using (input)
|
||||
return UnpackCps(input);
|
||||
}
|
||||
return input.AsStream;
|
||||
}*/
|
||||
Stream UnpackCps(IBinaryStream input)
|
||||
{
|
||||
var header = input.ReadHeader(0x10);
|
||||
int packed_size = header.ToInt32(4);
|
||||
int compression = header.ToUInt16(0xA);
|
||||
int unpacked_size = header.ToInt32(0xC);
|
||||
|
||||
//input.Seek(-4, SeekOrigin.End);
|
||||
input.Seek(packed_size - 4, SeekOrigin.Begin);
|
||||
uint key_offset = input.ReadUInt32() - 0x7534682;
|
||||
input.Position = key_offset;
|
||||
uint key = input.ReadUInt32() + key_offset + 0x3786425;
|
||||
|
||||
var decryptor = new CpsTransform(packed_size, (int)key_offset, key);
|
||||
using (var decoded = new InputCryptoStream(input.AsStream, decryptor))
|
||||
using (var cps = new BinaryStream(decoded, input.Name))
|
||||
{
|
||||
var output = new byte[unpacked_size];
|
||||
if ((compression & 1) != 0)
|
||||
{
|
||||
cps.ReadInt32();
|
||||
UnpackLnd(cps, output);
|
||||
}
|
||||
else if ((compression & 2) != 0)
|
||||
{
|
||||
UnpackLnd16(cps, output);
|
||||
}
|
||||
else
|
||||
{
|
||||
cps.ReadInt32();
|
||||
cps.Read(output, 0, unpacked_size);
|
||||
}
|
||||
return new BinMemoryStream(output);
|
||||
}
|
||||
}
|
||||
internal static void UnpackLnd(IBinaryStream input, byte[] output)
|
||||
{
|
||||
int unpacked_size = output.Length;
|
||||
int dst = 0;
|
||||
while (dst < unpacked_size)
|
||||
{
|
||||
int ctl = input.ReadByte();
|
||||
if (-1 == ctl)
|
||||
break;
|
||||
if ((ctl & 0x80) != 0)
|
||||
{
|
||||
if ((ctl & 0x40) != 0)
|
||||
{
|
||||
int count = (ctl & 0x1F) + 2;
|
||||
if ((ctl & 0x20) != 0)
|
||||
count += input.ReadUInt8() << 5;
|
||||
count = Math.Min(count, unpacked_size - dst);
|
||||
byte v = input.ReadUInt8();
|
||||
for (int i = 0; i < count; ++i)
|
||||
output[dst++] = v;
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = ((ctl >> 2) & 0xF) + 2;
|
||||
int offset = ((ctl & 3) << 8) + input.ReadUInt8() + 1;
|
||||
count = Math.Min(count, unpacked_size - dst);
|
||||
Binary.CopyOverlapped(output, dst - offset, dst, count);
|
||||
dst += count;
|
||||
}
|
||||
}
|
||||
else if ((ctl & 0x40) != 0)
|
||||
{
|
||||
int length = Math.Min((ctl & 0x3F) + 2, unpacked_size - dst);
|
||||
int count = input.ReadUInt8();
|
||||
input.Read(output, dst, length);
|
||||
dst += length;
|
||||
count = Math.Min(count * length, unpacked_size - dst);
|
||||
if (count > 0)
|
||||
{
|
||||
Binary.CopyOverlapped(output, dst - length, dst, count);
|
||||
dst += count;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = (ctl & 0x1F) + 1;
|
||||
if ((ctl & 0x20) != 0)
|
||||
count += input.ReadUInt8() << 5;
|
||||
count = Math.Min(count, unpacked_size - dst);
|
||||
input.Read(output, dst, count);
|
||||
dst += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
static void UnpackLnd16(IBinaryStream input, byte[] output)
|
||||
{
|
||||
throw new NotImplementedException("KID Lnd16 compression not implemented.");
|
||||
}
|
||||
}
|
||||
}
|
||||
207
ArcFormats/Kid/ImageBIP.cs
Normal file
207
ArcFormats/Kid/ImageBIP.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Media3D;
|
||||
|
||||
namespace GameRes.Formats.Kid
|
||||
{
|
||||
public class BipFormat : ImageFormat
|
||||
{
|
||||
public override string Tag { get { return "BIP/PS2"; } }
|
||||
public override string Description { get { return "PS2 tiled bitmap format"; } }
|
||||
public override uint Signature { get { return 0; } } //0x05000000
|
||||
|
||||
public override ImageMetaData ReadMetaData(IBinaryStream file)
|
||||
{
|
||||
uint header = file.ReadUInt32();
|
||||
uint real_sign;
|
||||
bool multi = false; // not implemented
|
||||
if (header < 5)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (header == 5)
|
||||
{
|
||||
real_sign = 0x14;
|
||||
}
|
||||
else if (header >= 6 && header <= 0x0C)
|
||||
{
|
||||
real_sign = header * 4;
|
||||
multi = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null; // throw new NotSupportedException(string.Format("BIP Chara format not supported."));
|
||||
}
|
||||
|
||||
file.Seek(0x10, SeekOrigin.Begin);
|
||||
uint fstart = file.ReadUInt16(); // usually 0x100
|
||||
|
||||
// Non-Sequential read
|
||||
file.Seek(0x88, SeekOrigin.Begin); // next one +20
|
||||
uint width = file.ReadUInt16();
|
||||
uint height = file.ReadUInt16();
|
||||
if (width > 2560 || height > 1440 || width < 16 || height < 16) // suppose not so large or so small
|
||||
return null;
|
||||
|
||||
file.Seek(real_sign, SeekOrigin.Begin); // usually 0x14
|
||||
if ((file.ReadUInt16() & 0x7FFF) != fstart)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
uint sign = file.ReadUInt16();
|
||||
uint dy;
|
||||
bool sliced = true;
|
||||
long denominator = file.Length;
|
||||
if (multi)
|
||||
{
|
||||
file.Seek(0xA2, SeekOrigin.Begin);
|
||||
uint f2start = file.ReadUInt16();
|
||||
denominator = f2start * 1024 + fstart + 0x100;
|
||||
}
|
||||
double oversize = (double)width * height * 4 / denominator;
|
||||
|
||||
file.Seek(0x90, SeekOrigin.Begin);
|
||||
//uint size = file.ReadUInt32(); // not size
|
||||
uint sizesign = file.ReadUInt16();
|
||||
uint sizesign_high = file.ReadUInt16();
|
||||
//size &= 0x00FFFFFF;
|
||||
//if (sign == 0x13 && size != (file.Length - 0x100) || sign == 0x17 && size != (file.Length - 0x100)* 2)
|
||||
if (sizesign != 0 || sizesign_high == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
uint pixelsize = (sizesign_high & 0x00FF) * 0x10000; /*r16: 2*pixelsize r32: pixelsize*/
|
||||
|
||||
if (oversize > 0.87 /*sign == 0x13*/)
|
||||
{
|
||||
// no-sliced oversize min: 0.87178 max: 0.9866
|
||||
// sliced oversize max: 0.8333 min: 0.76
|
||||
dy = 16;
|
||||
sliced = false;
|
||||
// sign = focusT / 2
|
||||
}
|
||||
else if (sign > 0x50)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (((double)pixelsize / denominator) <= 1.045 && sign != 0x2E && sign != 0x33)
|
||||
{
|
||||
/*known oversize 1.02 for expected <=1, noticed smallest r16 oversize 1.045+ for MOAR2 EV_SP04A, 0.99+ for MOAR1 OM2_TT13A, 0.89+ for MOAR3 C3_30A*/
|
||||
//sign % 2 == 0 && sign <= 0x2A && sign != 0x22 && sign != 0x1A
|
||||
//sign == 0x16 || sign == 0x20 || sign == 0x2A
|
||||
dy = 32;
|
||||
// sign = focusT * 2
|
||||
}
|
||||
else
|
||||
{
|
||||
//sign == 0x17 || sign == 0x19 || sign == 0x1F || sign == 0x25 || sign == 0x2E || sign == 0x34
|
||||
dy = 16;
|
||||
// sign = focusT / 2
|
||||
}
|
||||
|
||||
// uint dx = dy * 32;
|
||||
|
||||
return new BipImageMetaData
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
BlockSize = dy,
|
||||
Sliced = sliced,
|
||||
Multi = multi,
|
||||
};
|
||||
}
|
||||
public override ImageData Read(IBinaryStream file, ImageMetaData info)
|
||||
{
|
||||
if (info == null)
|
||||
throw new NotSupportedException(string.Format("Not BIP texture format."));
|
||||
var bipheader = (BipImageMetaData)info;
|
||||
file.Seek(0x10, SeekOrigin.Begin);
|
||||
uint fstart = file.ReadUInt16(); // usually 0x100
|
||||
file.Seek(fstart, SeekOrigin.Begin);
|
||||
byte[] pixels = new byte[bipheader.iWidth * bipheader.iHeight * 4];
|
||||
uint dy = bipheader.BlockSize;
|
||||
uint dx = dy * 32;
|
||||
if (bipheader.Sliced)
|
||||
{
|
||||
long dwidth = ((bipheader.iWidth + (dy - 2) - 1) / (dy - 2)) * dy;
|
||||
long dheight = ((bipheader.iHeight + (dy - 2) - 1) / (dy - 2)) * dy;
|
||||
long focus_H = (dwidth* dheight + dx - 1) / dx;
|
||||
long focus_T = (focus_H + dy - 1) / dy;
|
||||
|
||||
for (int t = 0; t < focus_T; t++)
|
||||
{
|
||||
for(int y = 0; y < dy; y++)
|
||||
{
|
||||
for (int x = 0; x < dx; x++)
|
||||
{
|
||||
var pixel = file.ReadBytes(4); //RGBA with wrong A
|
||||
long i2x = x + t * dx;
|
||||
long i3t = i2x / dwidth;
|
||||
long i3x = i2x - i3t * dwidth;
|
||||
long i3y = i3t * (dy - 2) + y;
|
||||
long i4x = i3x - i3x / dy * dy + i3x / dy * (dy - 2);
|
||||
if (i3x >= dwidth || i4x >= bipheader.iWidth || i3y >= bipheader.iHeight)
|
||||
continue;
|
||||
long target = (i4x + i3y * bipheader.iWidth) * 4;
|
||||
//BGRA
|
||||
pixels[target] = pixel[2];
|
||||
pixels[target + 1] = pixel[1];
|
||||
pixels[target + 2] = pixel[0];
|
||||
if (pixel[3] >= byte.MaxValue / 2)
|
||||
pixels[target + 3] = byte.MaxValue;
|
||||
else
|
||||
pixels[target + 3] = (byte)(pixel[3] << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
long focus_H = (bipheader.iWidth * bipheader.iHeight + dx - 1) / dx;
|
||||
long focus_T = (focus_H + dy - 1) / dy;
|
||||
for (int t = 0; t < focus_T; t++)
|
||||
{
|
||||
for (int y = 0; y < dy; y++)
|
||||
{
|
||||
for (int x = 0; x < dx; x++)
|
||||
{
|
||||
var pixel = file.ReadBytes(4); //RGBA with wrong A
|
||||
long i2x = x + t * dx;
|
||||
long i3t = i2x / bipheader.iWidth;
|
||||
long i3x = i2x - i3t * bipheader.iWidth;
|
||||
long i3y = i3t * dy + y;
|
||||
if (i3x >= bipheader.iWidth || i3y >= bipheader.iHeight)
|
||||
continue;
|
||||
long target = (i3x + i3y * bipheader.iWidth) * 4;
|
||||
//BGRA
|
||||
pixels[target] = pixel[2];
|
||||
pixels[target + 1] = pixel[1];
|
||||
pixels[target + 2] = pixel[0];
|
||||
if (pixel[3] >= byte.MaxValue / 2)
|
||||
pixels[target + 3] = byte.MaxValue;
|
||||
else
|
||||
pixels[target + 3] = (byte)(pixel[3] << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ImageData.Create(info, PixelFormats.Bgra32, null, pixels); ;
|
||||
}
|
||||
public override void Write(Stream file, ImageData image)
|
||||
{
|
||||
throw new System.NotImplementedException("BipFormat.Write not implemented");
|
||||
}
|
||||
|
||||
class BipImageMetaData : ImageMetaData
|
||||
{
|
||||
public uint BlockSize { get; set; }
|
||||
public bool Sliced { get; set; }
|
||||
public bool Multi { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
44
ArcFormats/Kid/ImageBIParc.cs
Normal file
44
ArcFormats/Kid/ImageBIParc.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using GameRes.Compression;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
|
||||
namespace GameRes.Formats.Kid
|
||||
{
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class BipArkFormat: BipFormat
|
||||
{
|
||||
public override string Tag { get { return "BIP/PS2 compressed"; } }
|
||||
public override string Description { get { return "PS2 tiled bitmap format with lzss compress"; } }
|
||||
public override uint Signature { get { return 0; } }
|
||||
public BipArkFormat()
|
||||
{
|
||||
Extensions = new string[] { "bip" };
|
||||
}
|
||||
|
||||
public override ImageMetaData ReadMetaData(IBinaryStream stream)
|
||||
{
|
||||
uint unpacked_size = stream.Signature;
|
||||
if (unpacked_size <= 0x20 || unpacked_size > 0x5000000) // ~83MB
|
||||
return null;
|
||||
stream.Position = 4;
|
||||
using (var lzss = new LzssStream(stream.AsStream, LzssMode.Decompress, true))
|
||||
using (var input = new SeekableStream(lzss))
|
||||
using (var bip = new BinaryStream(input, stream.Name))
|
||||
return base.ReadMetaData(bip);
|
||||
}
|
||||
public override ImageData Read(IBinaryStream stream, ImageMetaData info)
|
||||
{
|
||||
stream.Position = 4;
|
||||
using (var lzss = new LzssStream(stream.AsStream, LzssMode.Decompress, true))
|
||||
using (var input = new SeekableStream(lzss))
|
||||
using (var bip = new BinaryStream(input, stream.Name))
|
||||
return base.Read(bip, info);
|
||||
}
|
||||
public override void Write(Stream file, ImageData image)
|
||||
{
|
||||
throw new System.NotImplementedException("BipFormat.Write not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
111
ArcFormats/Kid/ImageLBG.cs
Normal file
111
ArcFormats/Kid/ImageLBG.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media;
|
||||
using System.IO;
|
||||
|
||||
namespace GameRes.Formats.Kid
|
||||
{
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class LbgFormat : ImageFormat
|
||||
{
|
||||
public override string Tag { get { return "LBG/PS2-SPC"; } }
|
||||
public override string Description { get { return "KID PS2 SPC Image Format"; } }
|
||||
public override uint Signature { get { return 0; } } //real signature may exist below
|
||||
|
||||
public LbgFormat()
|
||||
{
|
||||
Extensions = new string[] { "", "lbg" };
|
||||
// Actually LBG format can contain more than one image as lip for chara, well, "not implemented".
|
||||
}
|
||||
|
||||
public override ImageMetaData ReadMetaData(IBinaryStream file)
|
||||
{
|
||||
uint header = file.ReadUInt32();
|
||||
uint overheader = 0;
|
||||
|
||||
if (header != 0x2047424C) //LBG\x20
|
||||
{
|
||||
overheader = header;
|
||||
if (overheader > 0x50) //usually 10 or 30, never seen any larger
|
||||
return null;
|
||||
file.Seek(overheader, SeekOrigin.Begin);
|
||||
header = file.ReadUInt32();
|
||||
if (header != 0x2047424C) //LBG\x20
|
||||
return null;
|
||||
}
|
||||
|
||||
int width = file.ReadInt16();
|
||||
int height = file.ReadInt16();
|
||||
if (width <= 0 || height <= 0)
|
||||
return null;
|
||||
|
||||
return new LbgImageMetaData
|
||||
{
|
||||
Width = (uint)width,
|
||||
Height = (uint)height,
|
||||
OverHeader = overheader,
|
||||
};
|
||||
}
|
||||
public override ImageData Read(IBinaryStream file, ImageMetaData info)
|
||||
{
|
||||
if (info == null)
|
||||
throw new NotSupportedException(string.Format("Not LBG texture format."));
|
||||
|
||||
/*if (info.Width != 640)
|
||||
{
|
||||
throw new NotSupportedException(string.Format("Not 640 pixels width, may not work."));
|
||||
}*/
|
||||
var lbgheader = (LbgImageMetaData)info;
|
||||
uint oversize = 0;
|
||||
uint blocknum = info.Width / 126;
|
||||
if (lbgheader.OverHeader != 0)
|
||||
{
|
||||
file.Position = 4;
|
||||
uint filesize = file.ReadUInt32();
|
||||
if (filesize == 0)
|
||||
oversize = 8;
|
||||
else
|
||||
{
|
||||
filesize -= 16;
|
||||
oversize = (filesize - info.Width * info.Height * 4) / info.Height / (blocknum + 1);
|
||||
}
|
||||
}
|
||||
file.Position = lbgheader.OverHeader + 0x10;
|
||||
//List<byte> pixels = new List<byte>();
|
||||
byte[] pixels = new byte[info.Width * info.Height * 4];
|
||||
for (int blockcount = 0; blockcount <= blocknum; blockcount++)
|
||||
{
|
||||
for (int y = 0; y < info.Height; y++)
|
||||
{
|
||||
for (int x = 0; (blockcount != blocknum & x < 126) | (blockcount == blocknum & x < info.Width - blockcount * 126); x++)
|
||||
{
|
||||
var pixel = file.ReadBytes(4); //RGBA with wrong A
|
||||
long target = (x + blockcount * 126) * 4 + y * info.Width * 4;
|
||||
//BGRA
|
||||
pixels[target] = pixel[2];
|
||||
pixels[target + 1] = pixel[1];
|
||||
pixels[target + 2] = pixel[0];
|
||||
if (pixel[3] >= byte.MaxValue / 2)
|
||||
pixels[target + 3] = byte.MaxValue;
|
||||
else
|
||||
pixels[target + 3] = (byte)(pixel[3] << 1); //needs a test
|
||||
}
|
||||
if (oversize != 0)
|
||||
file.ReadBytes((int)oversize);
|
||||
}
|
||||
}
|
||||
return ImageData.Create(info, PixelFormats.Bgra32, null, pixels);
|
||||
}
|
||||
|
||||
public override void Write(Stream file, ImageData bitmap)
|
||||
{
|
||||
throw new NotImplementedException("LbgFormat.Write not implemented");
|
||||
}
|
||||
}
|
||||
class LbgImageMetaData : ImageMetaData
|
||||
{
|
||||
public uint OverHeader { get; set; }
|
||||
}
|
||||
}
|
||||
44
ArcFormats/Kid/ImageSPC.cs
Normal file
44
ArcFormats/Kid/ImageSPC.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using GameRes.Compression;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
|
||||
namespace GameRes.Formats.Kid
|
||||
{
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class SpcFormat: LbgFormat
|
||||
{
|
||||
public override string Tag { get { return "SPC/PS2"; } }
|
||||
public override string Description { get { return "PS2 CRI MiddleWare compressed texture format"; } }
|
||||
public override uint Signature { get { return 0; } }
|
||||
public SpcFormat()
|
||||
{
|
||||
Extensions = new string[] { "spc" };
|
||||
}
|
||||
|
||||
public override ImageMetaData ReadMetaData(IBinaryStream stream)
|
||||
{
|
||||
uint unpacked_size = stream.Signature;
|
||||
if (unpacked_size <= 0x20 || unpacked_size > 0x5000000) // ~83MB
|
||||
return null;
|
||||
stream.Position = 4;
|
||||
using (var lzss = new LzssStream(stream.AsStream, LzssMode.Decompress, true))
|
||||
using (var input = new SeekableStream(lzss))
|
||||
using (var lbg = new BinaryStream(input, stream.Name))
|
||||
return base.ReadMetaData(lbg);
|
||||
}
|
||||
|
||||
public override ImageData Read(IBinaryStream stream, ImageMetaData info)
|
||||
{
|
||||
stream.Position = 4;
|
||||
using (var lzss = new LzssStream(stream.AsStream, LzssMode.Decompress, true))
|
||||
using (var input = new SeekableStream(lzss))
|
||||
using (var lbg = new BinaryStream(input, stream.Name))
|
||||
return base.Read(lbg, info);
|
||||
}
|
||||
|
||||
public override void Write(Stream file, ImageData image)
|
||||
{
|
||||
throw new System.NotImplementedException("SpcFormat.Write not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,7 +479,7 @@ NextEntry:
|
||||
using (var writer = new BinaryWriter (output, Encoding.ASCII, true))
|
||||
{
|
||||
writer.Write (s_xp3_header);
|
||||
if (2 == xp3_options.Version)
|
||||
if (2 == xp3_options.Version || 3 == xp3_options.Version)
|
||||
{
|
||||
writer.Write ((long)0x17);
|
||||
writer.Write ((int)1);
|
||||
@@ -516,7 +516,7 @@ NextEntry:
|
||||
&& !(scheme.StartupTjsNotEncrypted && VFS.IsPathEqualsToFileName (name, "startup.tjs"))
|
||||
};
|
||||
bool compress = compress_contents && ShouldCompressFile (entry);
|
||||
using (var file = File.Open (name, FileMode.Open, FileAccess.Read))
|
||||
using (var file = File.Open (name, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
if (!xp3entry.IsEncrypted || 0 == file.Length)
|
||||
RawFileCopy (file, xp3entry, output, compress);
|
||||
@@ -538,20 +538,46 @@ NextEntry:
|
||||
callback (callback_count++, null, arcStrings.MsgWritingIndex);
|
||||
|
||||
long dir_pos = 0;
|
||||
if (3 == xp3_options.Version)
|
||||
{
|
||||
foreach (var entry in dir)
|
||||
{
|
||||
header.Write ((uint)0x6e666e68); // "hnfn"
|
||||
header.Write ((long)(4+2+entry.Name.Length*2));
|
||||
header.Write ((uint)entry.Hash);
|
||||
header.Write ((short)entry.Name.Length);
|
||||
foreach (char c in entry.Name)
|
||||
header.Write (c);
|
||||
}
|
||||
dir_pos = header.BaseStream.Position;
|
||||
}
|
||||
foreach (var entry in dir)
|
||||
{
|
||||
var entry_name = entry.Name;
|
||||
if (3 == xp3_options.Version)
|
||||
{
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
var text_bytes = Encoding.Unicode.GetBytes(entry.Name.ToLowerInvariant());
|
||||
var hash = md5.ComputeHash(text_bytes);
|
||||
var sb = new StringBuilder(32);
|
||||
for (int i = 0; i < hash.Length; ++i)
|
||||
sb.AppendFormat("{0:x2}", hash[i]);
|
||||
entry_name = sb.ToString();
|
||||
}
|
||||
}
|
||||
header.BaseStream.Position = dir_pos;
|
||||
header.Write ((uint)0x656c6946); // "File"
|
||||
long header_size_pos = header.BaseStream.Position;
|
||||
header.Write ((long)0);
|
||||
header.Write ((uint)0x6f666e69); // "info"
|
||||
header.Write ((long)(4+8+8+2 + entry.Name.Length*2));
|
||||
header.Write ((long)(4+8+8+2 + entry_name.Length*2));
|
||||
header.Write ((uint)(use_encryption ? 0x80000000 : 0));
|
||||
header.Write ((long)entry.UnpackedSize);
|
||||
header.Write ((long)entry.Size);
|
||||
|
||||
header.Write ((short)entry.Name.Length);
|
||||
foreach (char c in entry.Name)
|
||||
header.Write ((short)entry_name.Length);
|
||||
foreach (char c in entry_name)
|
||||
header.Write (c);
|
||||
|
||||
header.Write ((uint)0x6d676573); // "segm"
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
xmlns:s="clr-namespace:GameRes.Formats.Strings"
|
||||
xmlns:p="clr-namespace:GameRes.Formats.Properties"
|
||||
xmlns:local="clr-namespace:GameRes.Formats.GUI">
|
||||
<Grid.Resources>
|
||||
<local:Xp3VersionConverter x:Key="Xp3VersionConverter"></local:Xp3VersionConverter>
|
||||
</Grid.Resources>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
@@ -14,9 +17,10 @@
|
||||
<StackPanel Orientation="Vertical" Margin="0,0,10,5">
|
||||
<Label Content="{x:Static s:arcStrings.XP3LabelVersion}" Target="{Binding ElementName=Version}" Padding="4,0,0,5"/>
|
||||
<ComboBox Name="Version" Width="80" HorizontalAlignment="Left" SelectedValuePath="Content"
|
||||
SelectedValue="{Binding Source={x:Static p:Settings.Default}, Path=XP3Version, Mode=TwoWay}">
|
||||
SelectedValue="{Binding Source={x:Static p:Settings.Default}, Path=XP3Version, Mode=TwoWay, Converter={StaticResource Xp3VersionConverter}}">
|
||||
<ComboBoxItem Content="1"/>
|
||||
<ComboBoxItem Content="2"/>
|
||||
<ComboBoxItem Content="Z"/>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical" Margin="0,0,0,5">
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System.Windows;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace GameRes.Formats.GUI
|
||||
{
|
||||
@@ -13,4 +16,31 @@ namespace GameRes.Formats.GUI
|
||||
InitializeComponent ();
|
||||
}
|
||||
}
|
||||
|
||||
public class Xp3VersionConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var val = (int)value;
|
||||
switch (val)
|
||||
{
|
||||
case 1: return "1";
|
||||
case 2: return "2";
|
||||
case 3: return "Z";
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var str = value.ToString();
|
||||
switch (str)
|
||||
{
|
||||
case "1": return 1;
|
||||
case "2": return 2;
|
||||
case "Z": return 3;
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
50
ArcFormats/MAGES/ArcARC20.cs
Normal file
50
ArcFormats/MAGES/ArcARC20.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Text;
|
||||
|
||||
namespace GameRes.Formats.MAGES
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class ARC20Opener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "ARC/Princess Soft ARC20"; } }
|
||||
public override string Description { get { return "Princess Soft PS2 resource archive"; } }
|
||||
public override uint Signature { get { return 0x20435241; } } // 'ARC\x20'
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override ArcFile TryOpen(ArcView file)
|
||||
{
|
||||
int count = file.View.ReadInt32(4);
|
||||
if (!IsSaneCount(count))
|
||||
return null;
|
||||
uint filename_end = file.View.ReadUInt32(8);
|
||||
var dir = new List<Entry>(count);
|
||||
uint index_offset;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
index_offset = file.View.ReadUInt32(16 * i + 16);
|
||||
byte c;
|
||||
List<byte> namebyte = new List<byte>();
|
||||
while (true)
|
||||
{
|
||||
c = file.View.ReadByte((long)index_offset);
|
||||
if (c == 0 | index_offset > filename_end) break;
|
||||
namebyte.Add(c);
|
||||
index_offset++;
|
||||
}
|
||||
var sjis = System.Text.Encoding.GetEncoding("Shift-JIS");
|
||||
var name = sjis.GetString(namebyte.ToArray());
|
||||
var entry = Create<Entry>(name);
|
||||
|
||||
entry.Offset = file.View.ReadUInt32(16 * i + 16 + 4) * 2048;
|
||||
entry.Size = file.View.ReadUInt32(16 * i + 16 + 8);
|
||||
if (!entry.CheckPlacement(file.MaxOffset))
|
||||
return null;
|
||||
dir.Add(entry);
|
||||
}
|
||||
return new ArcFile(file, this, dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
ArcFormats/MAGES/ArcFARC.cs
Normal file
48
ArcFormats/MAGES/ArcFARC.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Text;
|
||||
|
||||
namespace GameRes.Formats.MAGES
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class FARCOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "DAT/FARC Rozen Maiden PS3 archive"; } }
|
||||
public override string Description { get { return "MAGES Rozen Maiden Wechseln Sie Welt ab PS3 BLJM61120 archive"; } }
|
||||
public override uint Signature { get { return 0x43524146; } } // 'FARC'
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override ArcFile TryOpen(ArcView file)
|
||||
{
|
||||
int count = file.View.ReadInt32(20);
|
||||
if (!IsSaneCount(count))
|
||||
return null;
|
||||
uint datanamestart = (uint)count * 20 + 36;
|
||||
var dir = new List<Entry>(count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
long namepl = file.View.ReadUInt32(20 * i + 36 + 16);
|
||||
long index_offset = datanamestart + namepl;
|
||||
byte c;
|
||||
List<byte> namebyte = new List<byte>();
|
||||
while (true)
|
||||
{
|
||||
c = file.View.ReadByte(index_offset);
|
||||
if (c == 0) break;
|
||||
namebyte.Add(c);
|
||||
index_offset++;
|
||||
}
|
||||
var sjis = Encoding.GetEncoding("Shift-JIS");
|
||||
var name = sjis.GetString(namebyte.ToArray());
|
||||
var entry = Create<Entry>(name);
|
||||
entry.Offset = file.View.ReadUInt32(20 * i + 36);
|
||||
entry.Size = file.View.ReadUInt32(20 * i + 36 + 4);
|
||||
if (entry.Size == 0)
|
||||
continue;
|
||||
dir.Add(entry);
|
||||
}
|
||||
return new ArcFile(file, this, dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
ArcFormats/MAGES/ArcGPDA.cs
Normal file
49
ArcFormats/MAGES/ArcGPDA.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Text;
|
||||
|
||||
namespace GameRes.Formats.MAGES
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class GpdaOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "DAT/GPDA"; } }
|
||||
public override string Description { get { return "PCSG00543 resource archive"; } }
|
||||
public override uint Signature { get { return 0x41445047; } } // 'GPDA'
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override ArcFile TryOpen(ArcView file)
|
||||
{
|
||||
int count = file.View.ReadInt32(0x0C);
|
||||
if (!IsSaneCount(count))
|
||||
return null;
|
||||
var dir = new List<Entry>(count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
uint filename_offset = file.View.ReadUInt32(16 * i + 16 + 12);
|
||||
uint filename_length = file.View.ReadUInt32(filename_offset) - 1;
|
||||
string name = file.View.ReadString(filename_offset + 4, filename_length);
|
||||
/*byte c;
|
||||
List<byte> namebyte = new List<byte>();
|
||||
while (true)
|
||||
{
|
||||
c = file.View.ReadByte((long)index_offset);
|
||||
if (c == 0 | index_offset > filename_end) break;
|
||||
namebyte.Add(c);
|
||||
index_offset++;
|
||||
}*/
|
||||
//var sjis = System.Text.Encoding.GetEncoding("Shift-JIS");
|
||||
//var name = Encoding.ASCII.GetString(namebyte.ToArray());
|
||||
var entry = Create<Entry>(name);
|
||||
|
||||
entry.Offset = file.View.ReadUInt32(16 * i + 16);
|
||||
entry.Size = file.View.ReadUInt32(16 * i + 16 + 8);
|
||||
if (!entry.CheckPlacement(file.MaxOffset))
|
||||
return null;
|
||||
dir.Add(entry);
|
||||
}
|
||||
return new ArcFile(file, this, dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
116
ArcFormats/MAGES/ArcGTF.cs
Normal file
116
ArcFormats/MAGES/ArcGTF.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using GameRes.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace GameRes.Formats.MAGES
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class GTFOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "GTF/Rozen Maiden PS3 Image archive"; } }
|
||||
public override string Description { get { return "MAGES Rozen Maiden Wechseln Sie Welt ab PS3 BLJM61120 Image archive"; } }
|
||||
public override uint Signature { get { return 0xFF000202; } } // 'FF000202'
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override ArcFile TryOpen(ArcView file)
|
||||
{
|
||||
int count = Binary.BigEndian(file.View.ReadInt32(8));
|
||||
if (!IsSaneCount(count))
|
||||
return null;
|
||||
string filename = Path.GetFileNameWithoutExtension(file.Name);
|
||||
var dir = new List<Entry>(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var entry = Create<Entry_RawImage>(filename + '_' + i.ToString());
|
||||
entry.Offset = Binary.BigEndian(file.View.ReadUInt32(16 + 36 * i));
|
||||
entry.Size = Binary.BigEndian(file.View.ReadUInt32(16 + 36 * i + 4));
|
||||
entry.width = Binary.BigEndian(file.View.ReadUInt16(32 + 36 * i));
|
||||
entry.height = Binary.BigEndian(file.View.ReadUInt16(32 + 36 * i + 2));
|
||||
entry.Type = "image";
|
||||
dir.Add(entry);
|
||||
}
|
||||
return new ArcFile(file, this, dir);
|
||||
}
|
||||
|
||||
public override Stream OpenEntry(ArcFile arc, Entry entry)
|
||||
{
|
||||
var compentry = (Entry_RawImage)entry;
|
||||
IBinaryStream input = arc.File.CreateStream(entry.Offset, entry.Size, entry.Name);
|
||||
return ReadImageGTF(input, compentry.width, compentry.height);
|
||||
}
|
||||
|
||||
internal class Entry_RawImage : Entry
|
||||
{
|
||||
public ushort width { get; set; }
|
||||
public ushort height { get; set; }
|
||||
}
|
||||
|
||||
/*[Export(typeof(ImageFormat))]
|
||||
public class GTFFormat : ImageFormat
|
||||
{
|
||||
public override string Tag { get { return "MAGES PS3/PSV Image Format"; } }
|
||||
public override string Description { get { return "MAGES PS3/PSV Image Format"; } }
|
||||
public override uint Signature { get { return 0; } }
|
||||
|
||||
public override ImageMetaData ReadMetaData(IBinaryStream file)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
public override ImageData Read(IBinaryStream file, ImageMetaData info)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
public override void Write(Stream file, ImageData bitmap)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// This method returns MAGES/ImageBIN format Image data stream, please ensure it's decoding method exists.
|
||||
/// </summary>
|
||||
/// <param name="input">GTF or BIN raw ARGB32 data, start in the upper left corner, each row.</param>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
/// <returns>MAGES/ImageBIN format Image data stream</returns>
|
||||
static Stream ReadImageGTF(IBinaryStream input, ushort width, ushort height)
|
||||
{
|
||||
//List<byte> bytes = new List<byte>();
|
||||
byte[] widths = BitConverter.GetBytes(width);
|
||||
byte[] heights = BitConverter.GetBytes(height);
|
||||
byte[] bpp = BitConverter.GetBytes((uint)32);
|
||||
/*Stream output = new MemoryStream();
|
||||
output.Write(widths, 0, widths.Length);
|
||||
output.Write(heights, 0, heights.Length);
|
||||
output.Write(bpp, 0, bpp.Length);
|
||||
output.Write(input.ReadBytes((int)input.Length), 0, (int)input.Length);
|
||||
byte[] bytes = new byte[output.Length];
|
||||
output.Seek(0, SeekOrigin.Begin);
|
||||
output.Read(bytes, 0, bytes.Length);
|
||||
output.Dispose();*/
|
||||
|
||||
byte[] inputData = input.ReadBytes((int)input.Length);
|
||||
byte[] outputData = new byte[widths.Length + heights.Length + bpp.Length + inputData.Length];
|
||||
Buffer.BlockCopy(widths, 0, outputData, 0, widths.Length);
|
||||
Buffer.BlockCopy(heights, 0, outputData, widths.Length, heights.Length);
|
||||
Buffer.BlockCopy(bpp, 0, outputData, widths.Length + heights.Length, bpp.Length);
|
||||
Buffer.BlockCopy(inputData, 0, outputData, widths.Length + heights.Length + bpp.Length, inputData.Length);
|
||||
return new BinMemoryStream(outputData);
|
||||
|
||||
/*List<byte> pixels = new List<byte>();
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
var pixel = input.ReadBytes(4); //ARGB
|
||||
//BGRA
|
||||
pixels.Add(pixel[3]);
|
||||
pixels.Add(pixel[2]);
|
||||
pixels.Add(pixel[1]);
|
||||
pixels.Add(pixel[0]);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace GameRes.Formats.NitroPlus
|
||||
namespace GameRes.Formats.MAGES
|
||||
{
|
||||
internal class LayEntry : Entry
|
||||
{
|
||||
57
ArcFormats/MAGES/ArcLoveOnce.cs
Normal file
57
ArcFormats/MAGES/ArcLoveOnce.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace GameRes.Formats.MAGES
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class LoveOnceOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "BIN/LoveOnce"; } }
|
||||
public override string Description { get { return "L@ve Once PS3 resource archive"; } }
|
||||
public override uint Signature { get { return 0; } } // no header
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override ArcFile TryOpen(ArcView filedata)
|
||||
{
|
||||
ArcView filelist;
|
||||
if (filedata.Name.EndsWith("data.bin"))
|
||||
{
|
||||
string filepath = Path.GetDirectoryName(filedata.Name);
|
||||
filepath = Path.Combine(filepath, "list.bin");
|
||||
if (! File.Exists(filepath)) return null;
|
||||
filelist = new ArcView(filepath);
|
||||
}
|
||||
else if (filedata.Name.EndsWith("list.bin"))
|
||||
{
|
||||
string filepath = Path.GetDirectoryName(filedata.Name);
|
||||
filepath = Path.Combine(filepath, "data.bin");
|
||||
if (!File.Exists(filepath)) return null;
|
||||
filelist = new ArcView(filepath);
|
||||
return TryOpen(filelist);
|
||||
}
|
||||
else return null;
|
||||
|
||||
uint index_offset = 0, count = 0;
|
||||
var dir = new List<Entry>();
|
||||
while (index_offset < filelist.MaxOffset)
|
||||
{
|
||||
uint filesize = filelist.View.ReadUInt32(index_offset);
|
||||
uint fileoffset = filelist.View.ReadUInt32(index_offset + 8);
|
||||
string name = filelist.View.ReadString(index_offset + 0x10, 0x40);
|
||||
var entry = Create<Entry>(name);
|
||||
entry.Size = filesize;
|
||||
entry.Offset = fileoffset;
|
||||
if (!entry.CheckPlacement(filedata.MaxOffset))
|
||||
return null;
|
||||
dir.Add(entry);
|
||||
count++;
|
||||
index_offset += 0x50;
|
||||
}
|
||||
return new ArcFile(filedata, this, dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
|
||||
namespace GameRes.Formats.NitroPlus
|
||||
namespace GameRes.Formats.MAGES
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class MpkOpener : ArchiveFormat
|
||||
@@ -39,22 +39,43 @@ namespace GameRes.Formats.NitroPlus
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
uint version_temp = file.View.ReadUInt32 (4);
|
||||
int count = file.View.ReadInt32 (8);
|
||||
if (!IsSaneCount (count))
|
||||
return null;
|
||||
uint index_offset = 0x48;
|
||||
var dir = new List<Entry> (count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
uint index_offset = 0;
|
||||
if (version_temp == 65536)
|
||||
{
|
||||
var name = file.View.ReadString (index_offset+0x18, 0xE0);
|
||||
var entry = Create<PackedEntry> (name);
|
||||
entry.Offset = file.View.ReadInt64 (index_offset);
|
||||
entry.Size = file.View.ReadUInt32 (index_offset+8);
|
||||
entry.UnpackedSize = file.View.ReadUInt32 (index_offset+0x10);
|
||||
if (!entry.CheckPlacement (file.MaxOffset))
|
||||
return null;
|
||||
dir.Add (entry);
|
||||
index_offset += 0x100;
|
||||
index_offset = 0x44;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
var name = file.View.ReadString (index_offset+0x1c, 0xE0);
|
||||
var entry = Create<PackedEntry> (name);
|
||||
entry.Offset = file.View.ReadInt32 (index_offset);
|
||||
entry.Size = file.View.ReadUInt32 (index_offset+4);
|
||||
entry.UnpackedSize = file.View.ReadUInt32 (index_offset+8);
|
||||
if (!entry.CheckPlacement (file.MaxOffset))
|
||||
return null;
|
||||
dir.Add (entry);
|
||||
index_offset += 0x100;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
index_offset = 0x48;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
var name = file.View.ReadString (index_offset+0x18, 0xE0);
|
||||
var entry = Create<PackedEntry> (name);
|
||||
entry.Offset = file.View.ReadInt64 (index_offset);
|
||||
entry.Size = file.View.ReadUInt32 (index_offset+8);
|
||||
entry.UnpackedSize = file.View.ReadUInt32 (index_offset+0x10);
|
||||
if (!entry.CheckPlacement (file.MaxOffset))
|
||||
return null;
|
||||
dir.Add (entry);
|
||||
index_offset += 0x100;
|
||||
}
|
||||
}
|
||||
return new ArcFile (file, this, dir);
|
||||
}
|
||||
143
ArcFormats/MAGES/ImageBIN.cs
Normal file
143
ArcFormats/MAGES/ImageBIN.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Diagnostics.Eventing.Reader;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace GameRes.Formats.MAGES
|
||||
{
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class BinFormat : ImageFormat
|
||||
{
|
||||
public override string Tag { get { return "MAGES PS3/PSV Image Format"; } }
|
||||
public override string Description { get { return "MAGES PS3/PSV Image Format"; } }
|
||||
public override uint Signature { get { return 0; } }
|
||||
public override bool CanWrite { get { return true; } }
|
||||
|
||||
public BinFormat()
|
||||
{
|
||||
Extensions = new string[] { "" };
|
||||
}
|
||||
|
||||
public override ImageMetaData ReadMetaData(IBinaryStream file)
|
||||
{
|
||||
//var header = file.ReadHeader(8);
|
||||
int width = file.ReadInt16();
|
||||
int height = file.ReadInt16();
|
||||
if (width <= 0 || height <= 0)
|
||||
return null;
|
||||
int bpp = file.ReadInt16();
|
||||
if (32 == bpp)
|
||||
{
|
||||
uint imagedatasize = (uint)width * (uint)height * 4;
|
||||
if (file.Length != imagedatasize + 8)
|
||||
return null;
|
||||
}
|
||||
else if (8 == bpp)
|
||||
{
|
||||
uint imagedatasize = (uint)width * (uint)height + 256;
|
||||
if (file.Length != imagedatasize + 8)
|
||||
return null;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
|
||||
return new ImageMetaData
|
||||
{
|
||||
Width = (uint)width,
|
||||
Height = (uint)height,
|
||||
BPP = bpp,
|
||||
};
|
||||
}
|
||||
public override ImageData Read(IBinaryStream file, ImageMetaData info)
|
||||
{
|
||||
if (info == null)
|
||||
throw new NotSupportedException(string.Format("Not BIN texture format."));
|
||||
if (info.BPP == 32)
|
||||
{
|
||||
uint pixelnum = info.Width * info.Height;
|
||||
if (file.Length != pixelnum * 4 + 8) throw new NotSupportedException(string.Format("Not BIN 32ARGB texture format."));
|
||||
|
||||
file.Position = 8;
|
||||
//var data = file.ReadBytes(info.iWidth * info.iHeight * 4);
|
||||
List<byte> pixels = new List<byte>();
|
||||
for (int i = 0; i < pixelnum; i++)
|
||||
{
|
||||
var pixel = file.ReadBytes(4); //ARGB
|
||||
//BGRA
|
||||
pixels.Add(pixel[3]);
|
||||
pixels.Add(pixel[2]);
|
||||
pixels.Add(pixel[1]);
|
||||
pixels.Add(pixel[0]);
|
||||
}
|
||||
return ImageData.Create(info, PixelFormats.Bgra32, null, pixels.ToArray());
|
||||
}
|
||||
else if (info.BPP == 8)
|
||||
{
|
||||
uint imagedatasize = info.Width * info.Height + 256;
|
||||
if (file.Length != imagedatasize + 8) throw new NotSupportedException(string.Format("Not BIN 256colors texture format."));
|
||||
file.Position = 8;
|
||||
//var pixelColor = file.ReadBytes(256 * 4);
|
||||
List<Color> colors = new List<Color>();
|
||||
for (int i = 0; i < 256; i += 4)
|
||||
{
|
||||
Color c = new Color();
|
||||
var color_b = file.ReadBytes(4); //BGRA
|
||||
c.B = color_b[0];
|
||||
c.G = color_b[1];
|
||||
c.R = color_b[2];
|
||||
c.A = color_b[3];
|
||||
colors.Add(c);
|
||||
}
|
||||
BitmapPalette palette = new BitmapPalette(colors);
|
||||
//file.Position += 256 * 4;
|
||||
var data = file.ReadBytes(info.iWidth * info.iHeight);
|
||||
return ImageData.Create(info, PixelFormats.Indexed8, palette, data);
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException(string.Format("Not BIN texture format."));
|
||||
}
|
||||
public override void Write(Stream stream, ImageData image)
|
||||
{
|
||||
//throw new System.NotImplementedException("BINFormat.Write not implemented");
|
||||
using (var file = new BinaryWriter(stream, Encoding.ASCII, true))
|
||||
{
|
||||
if (image.Width > ushort.MaxValue || image.Height > ushort.MaxValue)
|
||||
{
|
||||
throw new NotSupportedException(string.Format("Image width or height oversize."));
|
||||
}
|
||||
if (image.BPP != 32)
|
||||
{
|
||||
throw new NotSupportedException(string.Format("Image bitdepth not supported, should be 32."));
|
||||
}
|
||||
file.Write((ushort)image.Width);
|
||||
file.Write((ushort)image.Height);
|
||||
file.Write(image.BPP);
|
||||
|
||||
var bitmap = image.Bitmap;
|
||||
if (bitmap.Format != PixelFormats.Bgra32)
|
||||
{
|
||||
bitmap = new FormatConvertedBitmap(image.Bitmap, PixelFormats.Bgra32, null, 0);
|
||||
}
|
||||
int stride = (int)image.Width * 4;
|
||||
byte[] row_data = new byte[stride];
|
||||
Int32Rect rect = new Int32Rect(0, 0, (int)image.Width, 1);
|
||||
for (uint row = 0; row < image.Height; ++row)
|
||||
{
|
||||
bitmap.CopyPixels(rect, row_data, stride, 0);
|
||||
for (uint col = 0; col < image.Width; ++col)
|
||||
{
|
||||
(row_data[(col * 4) + 3], row_data[col * 4]) = (row_data[col * 4], row_data[(col * 4) + 3]);
|
||||
(row_data[(col * 4) + 2], row_data[(col * 4) + 1]) = (row_data[(col * 4) + 1], row_data[(col * 4) + 2]);
|
||||
}
|
||||
file.Write(row_data);
|
||||
rect.Y++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
173
ArcFormats/Musica/ArcPAK.cs
Normal file
173
ArcFormats/Musica/ArcPAK.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace GameRes.Formats.Musica
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class PakOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get; } = "PAK";
|
||||
public override string Description { get; } = "Musica engine legacy resource archive";
|
||||
public override uint Signature { get; } = 0;
|
||||
public override bool IsHierarchic { get; } = true;
|
||||
public override bool CanWrite { get; } = false;
|
||||
|
||||
public PakOpener()
|
||||
{
|
||||
Extensions = new[] { "pak" };
|
||||
ContainedFormats = new string[] { "PNG", "OGG" };
|
||||
}
|
||||
|
||||
static readonly HashSet<string> PakImageNames = new HashSet<string>()
|
||||
{
|
||||
"bg", "st",
|
||||
};
|
||||
|
||||
static readonly HashSet<string> PakAudioNames = new HashSet<string>()
|
||||
{
|
||||
"bgm", "se", "voice",
|
||||
};
|
||||
|
||||
private string GetType(string pakName, string entryName)
|
||||
{
|
||||
if (PakImageNames.Contains(pakName))
|
||||
{
|
||||
return "image";
|
||||
}
|
||||
|
||||
if (PakAudioNames.Contains(pakName))
|
||||
{
|
||||
return "audio";
|
||||
}
|
||||
|
||||
return FormatCatalog.Instance.GetTypeFromName(entryName, ContainedFormats);
|
||||
}
|
||||
|
||||
public override ArcFile TryOpen(ArcView view)
|
||||
{
|
||||
Stream input = view.CreateStream();
|
||||
using(input = new NegStream(input))
|
||||
{
|
||||
using(ArcView.Reader reader = new ArcView.Reader(input))
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
if (count <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Entry> entries = new List<Entry>(count);
|
||||
for(int i = 0; i < count; ++i)
|
||||
{
|
||||
uint indexLen = reader.ReadUInt32();
|
||||
long indexStart = input.Position;
|
||||
|
||||
string name = input.ReadCString();
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
uint size = reader.ReadUInt32();
|
||||
uint offset = reader.ReadUInt32();
|
||||
|
||||
Entry entry = new Entry() { Name = name, Offset = offset, Size = size };
|
||||
if (!entry.CheckPlacement(view.MaxOffset))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
entry.Type = this.GetType(Path.GetFileNameWithoutExtension(view.Name), entry.Name);
|
||||
entries.Add(entry);
|
||||
|
||||
input.Position = indexStart + indexLen;
|
||||
}
|
||||
return new PakArchive(view, this, entries, input.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override Stream OpenEntry(ArcFile arc, Entry entry)
|
||||
{
|
||||
if (!(arc is PakArchive pakArc))
|
||||
{
|
||||
return base.OpenEntry(arc, entry);
|
||||
}
|
||||
|
||||
return new NegStream(base.OpenEntry(arc, pakArc.GetEntry(entry)));
|
||||
}
|
||||
}
|
||||
|
||||
internal class PakArchive : ArcFile
|
||||
{
|
||||
private long m_IndexSize;
|
||||
public PakArchive(ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, long indexSize) : base(arc, impl, dir)
|
||||
{
|
||||
m_IndexSize = indexSize;
|
||||
}
|
||||
|
||||
public Entry GetEntry(Entry e)
|
||||
{
|
||||
return new Entry
|
||||
{
|
||||
Name = e.Name,
|
||||
Offset = e.Offset + m_IndexSize,
|
||||
Size = e.Size,
|
||||
Type = e.Type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//neg reg8
|
||||
public class NegStream : ProxyStream
|
||||
{
|
||||
public NegStream(Stream stream, bool leave_open = false) : base(stream, leave_open)
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int read = BaseStream.Read(buffer, offset, count);
|
||||
for (int i = 0; i < read; ++i)
|
||||
{
|
||||
buffer[offset + i] = (byte)-buffer[offset + i];
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
int b = BaseStream.ReadByte();
|
||||
if (-1 != b)
|
||||
{
|
||||
b = (byte)-b;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
byte[] write_buf;
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (null == write_buf)
|
||||
write_buf = new byte[81920];
|
||||
while (count > 0)
|
||||
{
|
||||
int chunk = Math.Min(write_buf.Length, count);
|
||||
for (int i = 0; i < chunk; ++i)
|
||||
{
|
||||
write_buf[i] = (byte)-buffer[offset + i];
|
||||
}
|
||||
BaseStream.Write(write_buf, 0, chunk);
|
||||
offset += chunk;
|
||||
count -= chunk;
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
BaseStream.WriteByte((byte)-value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,13 @@ namespace GameRes.Formats.Musica
|
||||
internal class SqzArchive : ArcFile
|
||||
{
|
||||
public readonly ImageMetaData Info;
|
||||
public readonly int FPS;
|
||||
|
||||
public SqzArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, ImageMetaData info)
|
||||
public SqzArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, ImageMetaData info, int fps)
|
||||
: base (arc, impl, dir)
|
||||
{
|
||||
Info = info;
|
||||
FPS = fps;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +55,7 @@ namespace GameRes.Formats.Musica
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
int count = file.View.ReadInt32 (0x10) * 2;
|
||||
int count = file.View.ReadInt32 (4);
|
||||
if (!IsSaneCount (count))
|
||||
return null;
|
||||
|
||||
@@ -62,8 +64,8 @@ namespace GameRes.Formats.Musica
|
||||
Width = file.View.ReadUInt32 (8),
|
||||
Height = file.View.ReadUInt32 (0xC),
|
||||
BPP = 32,
|
||||
// BPP = file.View.ReadInt32 (4),
|
||||
};
|
||||
int fps = file.View.ReadInt32(0x10);
|
||||
var base_name = Path.GetFileNameWithoutExtension (file.Name);
|
||||
uint index_offset = 0x14;
|
||||
var dir = new List<Entry> (count);
|
||||
@@ -80,7 +82,7 @@ namespace GameRes.Formats.Musica
|
||||
dir.Add (entry);
|
||||
index_offset += 8;
|
||||
}
|
||||
return new SqzArchive (file, this, dir, info);
|
||||
return new SqzArchive (file, this, dir, info, fps);
|
||||
}
|
||||
|
||||
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
|
||||
|
||||
@@ -42,6 +42,9 @@ namespace GameRes.Formats.NeXAS
|
||||
Huffman,
|
||||
Deflate,
|
||||
DeflateOrNone,
|
||||
None2,
|
||||
Zstd,
|
||||
ZstdOrNone,
|
||||
}
|
||||
|
||||
public class PacArchive : ArcFile
|
||||
@@ -67,13 +70,16 @@ namespace GameRes.Formats.NeXAS
|
||||
public PacOpener ()
|
||||
{
|
||||
Signatures = new uint[] { 0x00434150, 0 };
|
||||
Settings = new[] { PacEncoding };
|
||||
}
|
||||
|
||||
EncodingSetting PacEncoding = new EncodingSetting ("NexasEncodingCP", "DefaultEncoding");
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
if (!file.View.AsciiEqual (0, "PAC") || 'K' == file.View.ReadByte (3))
|
||||
return null;
|
||||
var reader = new IndexReader (file);
|
||||
var reader = new IndexReader (file, PacEncoding.Get<Encoding>());
|
||||
var dir = reader.Read();
|
||||
if (null == dir)
|
||||
return null;
|
||||
@@ -88,16 +94,18 @@ namespace GameRes.Formats.NeXAS
|
||||
ArcView m_file;
|
||||
int m_count;
|
||||
int m_pack_type;
|
||||
Encoding m_encoding;
|
||||
|
||||
const int MaxNameLength = 0x40;
|
||||
|
||||
public Compression PackType { get { return (Compression)m_pack_type; } }
|
||||
|
||||
public IndexReader (ArcView file)
|
||||
public IndexReader (ArcView file, Encoding enc)
|
||||
{
|
||||
m_file = file;
|
||||
m_count = file.View.ReadInt32 (4);
|
||||
m_pack_type = file.View.ReadInt32 (8);
|
||||
m_encoding = enc;
|
||||
}
|
||||
|
||||
List<Entry> m_dir;
|
||||
@@ -151,7 +159,7 @@ namespace GameRes.Formats.NeXAS
|
||||
m_dir.Clear();
|
||||
for (int i = 0; i < m_count; ++i)
|
||||
{
|
||||
var name = index.ReadCString (name_length);
|
||||
var name = index.ReadCString (name_length, m_encoding);
|
||||
if (string.IsNullOrWhiteSpace (name))
|
||||
return false;
|
||||
var entry = FormatCatalog.Instance.Create<PackedEntry> (name);
|
||||
@@ -160,7 +168,23 @@ namespace GameRes.Formats.NeXAS
|
||||
entry.Size = index.ReadUInt32();
|
||||
if (!entry.CheckPlacement (m_file.MaxOffset))
|
||||
return false;
|
||||
entry.IsPacked = m_pack_type != 0 && (m_pack_type != 4 || entry.Size != entry.UnpackedSize);
|
||||
switch (m_pack_type)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 6:
|
||||
{
|
||||
entry.IsPacked = true;
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
case 7:
|
||||
{
|
||||
entry.IsPacked = entry.Size != entry.UnpackedSize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_dir.Add (entry);
|
||||
}
|
||||
return true;
|
||||
@@ -188,8 +212,16 @@ namespace GameRes.Formats.NeXAS
|
||||
return new BinMemoryStream (unpacked, 0, (int)pent.UnpackedSize, entry.Name);
|
||||
}
|
||||
case Compression.Deflate:
|
||||
default:
|
||||
case Compression.DeflateOrNone:
|
||||
return new ZLibStream (input, CompressionMode.Decompress);
|
||||
case Compression.Zstd:
|
||||
case Compression.ZstdOrNone:
|
||||
{
|
||||
var unpacked = ZstdDecompress (input, pent.UnpackedSize);
|
||||
return new BinMemoryStream (unpacked, entry.Name);
|
||||
}
|
||||
default:
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,5 +231,15 @@ namespace GameRes.Formats.NeXAS
|
||||
var decoder = new HuffmanDecoder (packed, dst);
|
||||
return decoder.Unpack();
|
||||
}
|
||||
|
||||
static private byte[] ZstdDecompress (Stream s, uint unpackedSize)
|
||||
{
|
||||
using (var ds = new ZstdNet.DecompressionStream (s))
|
||||
{
|
||||
var dst = new byte[unpackedSize];
|
||||
ds.Read (dst, 0, dst.Length);
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
50
ArcFormats/NipponIchi/ArcCASN.cs
Normal file
50
ArcFormats/NipponIchi/ArcCASN.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using GameRes.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
|
||||
namespace GameRes.Formats.NipponIchi
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class CASNOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "DAT/CASN ShinHayarigami"; } }
|
||||
public override string Description { get { return "Nippon Ichi Shin Hayarigami PS3 resource archive"; } }
|
||||
public override uint Signature { get { return 0x4E534143; } } // 'CASN'
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override ArcFile TryOpen(ArcView file)
|
||||
{
|
||||
int count = Binary.BigEndian(file.View.ReadUInt16(6));
|
||||
if (!IsSaneCount(count))
|
||||
return null;
|
||||
long index_offset = count * 10 + 30; //actually count*10 + 28, ignored two bytes of 000C or 000B
|
||||
var dir = new List<Entry>(count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
uint fstart = Binary.BigEndian(file.View.ReadUInt32(index_offset));
|
||||
uint flength = Binary.BigEndian(file.View.ReadUInt32(index_offset + 4));
|
||||
index_offset += 8;
|
||||
byte c;
|
||||
List<byte> namebyte = new List<byte>();
|
||||
while (true)
|
||||
{
|
||||
c = file.View.ReadByte(index_offset);
|
||||
if (c == 0) break;
|
||||
namebyte.Add(c);
|
||||
index_offset++;
|
||||
}
|
||||
index_offset += 3;
|
||||
//var sjis = System.Text.Encoding.GetEncoding("Shift-JIS");
|
||||
var name = System.Text.Encoding.ASCII.GetString(namebyte.ToArray());
|
||||
var entry = Create<Entry>(name);
|
||||
entry.Offset = fstart;
|
||||
entry.Size = flength;
|
||||
if (!entry.CheckPlacement(file.MaxOffset))
|
||||
return null;
|
||||
dir.Add(entry);
|
||||
}
|
||||
return new ArcFile(file, this, dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
ArcFormats/NipponIchi/ArcPSFS.cs
Normal file
42
ArcFormats/NipponIchi/ArcPSFS.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using GameRes.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
|
||||
namespace GameRes.Formats.NipponIchi
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class PSFSOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "DAT/PS_FS ShinHayarigami2"; } }
|
||||
public override string Description { get { return "Nippon Ichi Shin Hayarigami2 PS3 resource archive"; } }
|
||||
public override uint Signature { get { return 0x465F5350; } } // 'PS_F' of 'PS_FS_V1'
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override ArcFile TryOpen(ArcView file)
|
||||
{
|
||||
int count = Binary.BigEndian(file.View.ReadInt32(8));
|
||||
if (!IsSaneCount(count))
|
||||
return null;
|
||||
long index_offset = 0x10;
|
||||
var dir = new List<Entry>(count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
string name = file.View.ReadString(index_offset, 0x30);
|
||||
var entry = Create<Entry>(name);
|
||||
entry.Offset = Binary.BigEndian(file.View.ReadUInt32(index_offset + 0x3C));
|
||||
entry.Size = Binary.BigEndian(file.View.ReadUInt32(index_offset + 0x34));
|
||||
index_offset += 0x40;
|
||||
if (!entry.CheckPlacement(file.MaxOffset))
|
||||
return null;
|
||||
if (0 == file.View.ReadByte(entry.Offset))
|
||||
{
|
||||
entry.Offset += 0x10;
|
||||
entry.Size -= 0x10;
|
||||
}
|
||||
dir.Add(entry);
|
||||
}
|
||||
return new ArcFile(file, this, dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
ArcFormats/NipponIchi/ImageNMT.cs
Normal file
39
ArcFormats/NipponIchi/ImageNMT.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace GameRes.Formats.NipponIchi
|
||||
{
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class NmtFormat : ImageFormat {
|
||||
public override string Tag { get { return "NMT"; } }
|
||||
public override string Description { get { return "NIS Multy Texform image format"; } }
|
||||
public override uint Signature { get { return 0x6D73696E; } } // 'nism'
|
||||
|
||||
public override ImageMetaData ReadMetaData(IBinaryStream file)
|
||||
{
|
||||
var header = file.ReadHeader(0x30);
|
||||
var signature = file.ReadHeader(15);
|
||||
if (System.Text.Encoding.ASCII.GetString(signature.ToArray()) != "nismultitexform")
|
||||
return null;
|
||||
return new ImageMetaData
|
||||
{
|
||||
Width = header.ToUInt16(0x26),
|
||||
Height = header.ToUInt16(0x28)
|
||||
};
|
||||
}
|
||||
public override ImageData Read(IBinaryStream file, ImageMetaData info)
|
||||
{
|
||||
//uint imagedatasize = info.Width * info.Height * 4;
|
||||
if (info == null) throw new NotSupportedException(string.Format("Not NMT texture format."));
|
||||
file.Position = 0x30;
|
||||
var data = file.ReadBytes(info.iWidth * info.iHeight * 4);
|
||||
return ImageData.Create(info, PixelFormats.Bgra32, null, data);
|
||||
}
|
||||
public override void Write(Stream file, ImageData image)
|
||||
{
|
||||
throw new System.NotImplementedException("NmtFormat.Write not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
26
ArcFormats/Properties/Settings.Designer.cs
generated
26
ArcFormats/Properties/Settings.Designer.cs
generated
@@ -12,7 +12,7 @@ namespace GameRes.Formats.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.0.3.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.0.0")]
|
||||
public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
@@ -813,5 +813,29 @@ namespace GameRes.Formats.Properties {
|
||||
this["AFAEncodingCP"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("DXARC")]
|
||||
public string DXAPassword {
|
||||
get {
|
||||
return ((string)(this["DXAPassword"]));
|
||||
}
|
||||
set {
|
||||
this["DXAPassword"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("932")]
|
||||
public int NexasEncodingCP {
|
||||
get {
|
||||
return ((int)(this["NexasEncodingCP"]));
|
||||
}
|
||||
set {
|
||||
this["NexasEncodingCP"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,5 +200,11 @@
|
||||
<Setting Name="AFAEncodingCP" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">932</Value>
|
||||
</Setting>
|
||||
<Setting Name="DXAPassword" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)">DXARC</Value>
|
||||
</Setting>
|
||||
<Setting Name="NexasEncodingCP" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">932</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
Binary file not shown.
@@ -54,7 +54,8 @@ namespace GameRes.Formats.Seraphim
|
||||
public override ImageMetaData ReadMetaData (IBinaryStream stream)
|
||||
{
|
||||
var header = stream.ReadHeader (0x10);
|
||||
if ('C' != header[0] || 'F' != header[1] || 0 != header[3])
|
||||
uint sig = header.ToUInt16(0);
|
||||
if (sig != Signature)
|
||||
return null;
|
||||
int packed_size = header.ToInt32 (12);
|
||||
if (packed_size <= 0 || packed_size > stream.Length-0x10)
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace GameRes.Formats.Valkyria
|
||||
|
||||
internal interface IMg2Scheme
|
||||
{
|
||||
Mg2EncryptedStream CreateStream (Stream main, int offset, int length);
|
||||
Mg2EncryptedStream CreateStream (Stream main, int offset, int length, int key);
|
||||
ImageData CreateImage (BitmapSource bitmap, ImageMetaData info);
|
||||
}
|
||||
|
||||
@@ -52,17 +52,17 @@ namespace GameRes.Formats.Valkyria
|
||||
public override string Description { get { return "Valkyria image format"; } }
|
||||
public override uint Signature { get { return 0x4F43494D; } } // 'MICO'
|
||||
|
||||
static readonly IMg2Scheme[] KnownSchemes = { new Mg2SchemeV1(), new Mg2SchemeV2() };
|
||||
static readonly IMg2Scheme[] KnownSchemes = { new Mg2SchemeV1(), new Mg2SchemeV2(), new Mg2SchemeV3() };
|
||||
|
||||
public override ImageMetaData ReadMetaData (IBinaryStream file)
|
||||
{
|
||||
var header = file.ReadHeader (0x10);
|
||||
if (!header.AsciiEqual (4, "CG01"))
|
||||
if (!header.AsciiEqual (4, "CG01") && !header.AsciiEqual (4, "CG02"))
|
||||
return null;
|
||||
int length = header.ToInt32 (8);
|
||||
foreach (var scheme in KnownSchemes)
|
||||
{
|
||||
using (var input = scheme.CreateStream (file.AsStream, 0x10, length))
|
||||
using (var input = scheme.CreateStream (file.AsStream, 0x10, length, length))
|
||||
using (var img = new BinaryStream (input, file.Name))
|
||||
{
|
||||
ImageFormat format;
|
||||
@@ -102,7 +102,7 @@ namespace GameRes.Formats.Valkyria
|
||||
BitmapSource ReadBitmapSource (Stream file, Mg2MetaData meta)
|
||||
{
|
||||
BitmapSource frame;
|
||||
using (var input = meta.Scheme.CreateStream (file, 0x10, meta.ImageLength))
|
||||
using (var input = meta.Scheme.CreateStream (file, 0x10, meta.ImageLength, meta.ImageLength))
|
||||
using (var img = new BinaryStream (input, meta.FileName))
|
||||
{
|
||||
var image = meta.Format.Read (img, meta);
|
||||
@@ -116,7 +116,7 @@ namespace GameRes.Formats.Valkyria
|
||||
var pixels = new byte[stride * (int)meta.Height];
|
||||
frame.CopyPixels (pixels, stride, 0);
|
||||
|
||||
using (var input = meta.Scheme.CreateStream (file, 0x10+meta.ImageLength, meta.AlphaLength))
|
||||
using (var input = meta.Scheme.CreateStream (file, 0x10+meta.ImageLength, meta.AlphaLength, meta.ImageLength))
|
||||
{
|
||||
var decoder = BitmapDecoder.Create (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
BitmapSource alpha_frame = decoder.Frames[0];
|
||||
@@ -148,32 +148,53 @@ namespace GameRes.Formats.Valkyria
|
||||
|
||||
internal class Mg2EncryptedStream : StreamRegion
|
||||
{
|
||||
readonly byte m_version;
|
||||
readonly int m_threshold;
|
||||
readonly byte m_key;
|
||||
readonly byte m_key0;
|
||||
readonly byte m_key1;
|
||||
|
||||
protected Mg2EncryptedStream (Stream main, int offset, int length, int threshold, byte key)
|
||||
protected Mg2EncryptedStream (Stream main, int offset, int length, byte version, int threshold, byte key0, byte key1)
|
||||
: base (main, offset, length, true)
|
||||
{
|
||||
m_version = version;
|
||||
m_threshold = threshold;
|
||||
m_key = key;
|
||||
m_key0 = key0;
|
||||
m_key1 = key1;
|
||||
}
|
||||
|
||||
public static Mg2EncryptedStream CreateV1 (Stream main, int offset, int length)
|
||||
{
|
||||
return new Mg2EncryptedStream (main, offset, length, length / 5, 0);
|
||||
return new Mg2EncryptedStream(main, offset, length, 1, length / 5, 0, 0);
|
||||
}
|
||||
|
||||
public static Mg2EncryptedStream CreateV2 (Stream main, int offset, int length)
|
||||
{
|
||||
return new Mg2EncryptedStream (main, offset, length, Math.Min (25, length), (byte)length);
|
||||
return new Mg2EncryptedStream( main, offset, length, 2, Math.Min(25, length), (byte)length, 0);
|
||||
}
|
||||
|
||||
public static Mg2EncryptedStream CreateV3 (Stream main, int offset, int length, int key)
|
||||
{
|
||||
return new Mg2EncryptedStream(main, offset, length, 3, 0, (byte)(key >> 1), (byte)((key & 1) + (key >> 3)));
|
||||
}
|
||||
|
||||
public override int Read (byte[] buffer, int offset, int count)
|
||||
{
|
||||
int pos = (int)Position;
|
||||
int read = base.Read (buffer, offset, count);
|
||||
for (int i = 0; i < read && pos < m_threshold; ++i)
|
||||
buffer[offset+i] ^= (byte)(m_key + pos++);
|
||||
long pos = Position;
|
||||
int read = base.Read(buffer, offset, count);
|
||||
|
||||
if (m_version == 3)
|
||||
{
|
||||
for (int i = 0; i < read; ++i)
|
||||
{
|
||||
buffer[offset+i] ^= (byte)((pos >> 4) ^ (pos + m_key0) ^ m_key1);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < read && pos < m_threshold; ++i)
|
||||
buffer[offset+i] ^= (byte)(m_key0 + pos++);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@@ -181,15 +202,24 @@ namespace GameRes.Formats.Valkyria
|
||||
{
|
||||
long pos = Position;
|
||||
int b = base.ReadByte();
|
||||
if (b != -1 && pos < m_threshold)
|
||||
b ^= (byte)(m_key + pos);
|
||||
|
||||
if (m_version == 3)
|
||||
{
|
||||
b ^= (byte)((pos >> 4) ^ (pos + m_key0) ^ m_key1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (b != -1 && pos < m_threshold)
|
||||
b ^= (byte)(m_key0 + pos);
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Mg2SchemeV1 : IMg2Scheme
|
||||
{
|
||||
public Mg2EncryptedStream CreateStream (Stream main, int offset, int length)
|
||||
public Mg2EncryptedStream CreateStream (Stream main, int offset, int length, int key)
|
||||
{
|
||||
return Mg2EncryptedStream.CreateV1 (main, offset, length);
|
||||
}
|
||||
@@ -203,7 +233,7 @@ namespace GameRes.Formats.Valkyria
|
||||
|
||||
internal class Mg2SchemeV2 : IMg2Scheme
|
||||
{
|
||||
public Mg2EncryptedStream CreateStream (Stream main, int offset, int length)
|
||||
public Mg2EncryptedStream CreateStream (Stream main, int offset, int length, int key)
|
||||
{
|
||||
return Mg2EncryptedStream.CreateV2 (main, offset, length);
|
||||
}
|
||||
@@ -215,4 +245,19 @@ namespace GameRes.Formats.Valkyria
|
||||
return new ImageData (frame, info);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Mg2SchemeV3 : IMg2Scheme
|
||||
{
|
||||
public Mg2EncryptedStream CreateStream (Stream main, int offset, int length, int key)
|
||||
{
|
||||
return Mg2EncryptedStream.CreateV3 (main, offset, length, key);
|
||||
}
|
||||
|
||||
public ImageData CreateImage (BitmapSource frame, ImageMetaData info)
|
||||
{
|
||||
frame = new TransformedBitmap(frame, new ScaleTransform { ScaleY = -1 });
|
||||
frame.Freeze();
|
||||
return new ImageData(frame, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace GameRes.Formats.YaneSDK
|
||||
entry.EncryptedSize = index.ReadUInt16();
|
||||
entry.Size = index.ReadUInt32();
|
||||
entry.Offset = index.ReadUInt32();
|
||||
if (!entry.CheckPlacement (file.MaxOffset) || entry.Offset <= data_offset)
|
||||
if (!entry.CheckPlacement (file.MaxOffset) || entry.Offset < data_offset)
|
||||
return null;
|
||||
dir.Add (entry);
|
||||
}
|
||||
|
||||
@@ -202,6 +202,12 @@
|
||||
<setting name="AFAEncodingCP" serializeAs="String">
|
||||
<value>932</value>
|
||||
</setting>
|
||||
<setting name="DXAPassword" serializeAs="String">
|
||||
<value>DXARC</value>
|
||||
</setting>
|
||||
<setting name="NexasEncodingCP" serializeAs="String">
|
||||
<value>932</value>
|
||||
</setting>
|
||||
</GameRes.Formats.Properties.Settings>
|
||||
</userSettings>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /></startup>
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Memory" version="4.5.4" targetFramework="net46" />
|
||||
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net46" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.6.0" targetFramework="net46" />
|
||||
<package id="System.Security.Cryptography.Algorithms" version="4.3.1" targetFramework="net46" />
|
||||
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.ValueTuple" version="4.5.0" targetFramework="net46" />
|
||||
</packages>
|
||||
<package id="ZstdNet" version="1.4.5" targetFramework="net46" />
|
||||
</packages>
|
||||
|
||||
@@ -100,6 +100,10 @@
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -29,8 +29,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Image.Convert", "Image.Conv
|
||||
{A8865685-27CC-427B-AC38-E48D2AD05DF4} = {A8865685-27CC-427B-AC38-E48D2AD05DF4}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SchemeBuilder", "SchemeBuilder\SchemeBuilder.csproj", "{B7E7EBFB-C06E-4FC8-9AF2-7CD132AB15FD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Experimental", "Experimental\Experimental.csproj", "{60054FD9-4472-4BB4-9E3D-2F80D3D22468}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Legacy", "Legacy\Legacy.csproj", "{C79E82A8-8D32-485D-8442-2D4F71FBB5D5}"
|
||||
@@ -76,10 +74,6 @@ Global
|
||||
{757EB8B1-F62C-4690-AC3D-DAE4A5576B3E}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
|
||||
{757EB8B1-F62C-4690-AC3D-DAE4A5576B3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{757EB8B1-F62C-4690-AC3D-DAE4A5576B3E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B7E7EBFB-C06E-4FC8-9AF2-7CD132AB15FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B7E7EBFB-C06E-4FC8-9AF2-7CD132AB15FD}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
|
||||
{B7E7EBFB-C06E-4FC8-9AF2-7CD132AB15FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B7E7EBFB-C06E-4FC8-9AF2-7CD132AB15FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{60054FD9-4472-4BB4-9E3D-2F80D3D22468}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{60054FD9-4472-4BB4-9E3D-2F80D3D22468}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{60054FD9-4472-4BB4-9E3D-2F80D3D22468}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace GameRes
|
||||
{
|
||||
public static class Encodings
|
||||
{
|
||||
public static readonly Encoding cp932 = Encoding.GetEncoding (932);
|
||||
public static readonly Encoding cp932 = Encoding.GetEncoding(932);
|
||||
|
||||
public static Encoding WithFatalFallback (this Encoding enc)
|
||||
{
|
||||
|
||||
@@ -170,6 +170,27 @@ namespace GameRes
|
||||
return bin;
|
||||
}
|
||||
|
||||
internal int FindEoS (int start, int length, Encoding enc)
|
||||
{
|
||||
int eos_pos = -1;
|
||||
if (enc.IsUtf16())
|
||||
{
|
||||
for (int i = start+1; i < start+length; i += 2)
|
||||
{
|
||||
if (m_buffer[i-1] == 0 && m_buffer[i] == 0)
|
||||
{
|
||||
eos_pos = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
eos_pos = Array.IndexOf<byte> (m_buffer, 0, start, length);
|
||||
}
|
||||
return eos_pos;
|
||||
}
|
||||
|
||||
uint ReadSignature ()
|
||||
{
|
||||
if (m_header_size >= 4)
|
||||
@@ -307,10 +328,7 @@ namespace GameRes
|
||||
public string ReadCString (int length, Encoding enc)
|
||||
{
|
||||
length = FillBuffer (length);
|
||||
int i;
|
||||
for (i = 0; i < length; ++i)
|
||||
if (0 == m_buffer[m_buffer_pos+i])
|
||||
break;
|
||||
int i = FindEoS(m_buffer_pos, length, enc) - m_buffer_pos;
|
||||
string s = enc.GetString (m_buffer, m_buffer_pos, i);
|
||||
m_buffer_pos += length;
|
||||
return s;
|
||||
|
||||
@@ -65,3 +65,10 @@ Korean translation by [mireado](https://github.com/mireado), [overworks](https:/
|
||||
Simplified Chinese translation by [elasticblitz](https://github.com/elasticblitz), [PeratX](https://github.com/PeratX) and [taroxd](https://github.com/taroxd)
|
||||
|
||||
Japanese translation by [haniwa55](https://github.com/haniwa55)
|
||||
|
||||
Contributors
|
||||
------
|
||||
|
||||
<a href="https://github.com/crskycode/GARbro/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=crskycode/GARbro" />
|
||||
</a>
|
||||
Reference in New Issue
Block a user