Skip to content

Commit 8f95b5e

Browse files
authored
Merge pull request OpenKH#1226 from Some1fromthedark/feature/kh1-iso-extraction
[WIP] Add Mods Manager support for PCSX2 kh1 and Recom
2 parents cfb1385 + 754a3da commit 8f95b5e

17 files changed

Lines changed: 2471 additions & 1343 deletions

OpenKh.Common/IsoUtility.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,11 @@ public static int GetFileOffset(Stream isoStream, string fileName)
4646

4747
return -1;
4848
}
49+
50+
public static Stream GetSectors(Stream isoStream, long startSector, int sectorCount)
51+
{
52+
isoStream.SetPosition(startSector * BlockLength);
53+
return new MemoryStream(isoStream.ReadBytes(sectorCount * BlockLength));
54+
}
4955
}
5056
}

OpenKh.Kh1/Img1.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,13 @@ public Stream FileOpen(string fileName)
5757

5858
public Stream FileOpen(Idx1 entry)
5959
{
60+
var startOffset = (_firstBlock + entry.IsoBlock) * IsoBlockAlign;
61+
if ((int)(startOffset + entry.Length) < 0 || startOffset + entry.Length != (int)(startOffset + entry.Length))
62+
return null;
63+
6064
if (entry.CompressionFlag != 0)
6165
{
62-
var fileStream = new SubStream(_stream, (_firstBlock + entry.IsoBlock) * IsoBlockAlign, entry.Length);
66+
var fileStream = new SubStream(_stream, startOffset, entry.Length);
6367
return new MemoryStream(Decompress(fileStream));
6468
}
6569

@@ -75,7 +79,7 @@ public static byte[] Decompress(byte[] srcData)
7579
{
7680
var srcIndex = srcData.Length - 1;
7781

78-
if (srcIndex == 0)
82+
if (srcIndex <= 2)
7983
return Array.Empty<byte>();
8084

8185
var key = srcData[srcIndex--];
@@ -96,7 +100,7 @@ public static byte[] Decompress(byte[] srcData)
96100
var copyLength = srcData[srcIndex--];
97101
for (int i = 0; i < copyLength + 3 && dstIndex >= 0; i++)
98102
{
99-
if (dstIndex + copyIndex + 1 < dstData.Length)
103+
if (dstIndex + copyIndex < dstData.Length)
100104
dstData[dstIndex--] = dstData[dstIndex + copyIndex + 1];
101105
else
102106
dstData[dstIndex--] = 0;

OpenKh.Recom/Dat.cs

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
using OpenKh.Common;
2+
using System.Net;
3+
using System.Reflection.Metadata.Ecma335;
4+
using Xe.BinaryMapper;
5+
6+
namespace OpenKh.Recom
7+
{
8+
public record RootDirInfo
9+
{
10+
public List<RootFileInfo> FilesInfo { get; set; } = new List<RootFileInfo>();
11+
public List<SubDirInfo> SubDirsInfo { get; set; } = new List<SubDirInfo>();
12+
13+
[Data] public byte BlockCount { get; set; }
14+
[Data(Count = 5)] public string Id { get; set; } = "";
15+
[Data] public ushort FileCount { get; set; }
16+
[Data] public ushort FilesInfoBlockCount { get; set; }
17+
18+
public static RootDirInfo Read(Stream stream)
19+
{
20+
var rdi = BinaryMapping.ReadObject<RootDirInfo>(stream);
21+
for (int i = 0; i < rdi.FileCount; i++)
22+
{
23+
RootFileInfo rfi = BinaryMapping.ReadObject<RootFileInfo>(stream.SetPosition(0x800 + i * 32));
24+
if (rfi.SubDirsInfoOffset != 0 && rfi.SubDirsCount > 0)
25+
{
26+
rfi.SubDirsInfoStartIndex = rdi.SubDirsInfo.Count;
27+
var name = rfi.Filename.Length >= 4 ? rfi.Filename.Substring(1, 3) : String.Empty;
28+
uint dir_offset;
29+
if (!uint.TryParse(name, out dir_offset))
30+
{
31+
// Skip this file
32+
continue;
33+
}
34+
dir_offset *= 10000;
35+
for (int j = 0; j < rfi.SubDirsCount; j++)
36+
{
37+
SubDirInfo sdi = BinaryMapping.ReadObject<SubDirInfo>(stream.SetPosition(0x800 + rfi.SubDirsInfoOffset + j * 32));
38+
sdi.DirId += dir_offset;
39+
rdi.SubDirsInfo.Add(sdi);
40+
}
41+
}
42+
rdi.FilesInfo.Add(rfi);
43+
}
44+
return rdi;
45+
}
46+
47+
public void ExtractFiles(Stream iso_stream, string extract_path, Action<float> onProgress = null)
48+
{
49+
if (onProgress != null)
50+
{
51+
onProgress(0.0f);
52+
}
53+
54+
if (!Directory.Exists(extract_path))
55+
{
56+
Directory.CreateDirectory(extract_path);
57+
}
58+
59+
var symlinks = new List<(String, String, String, uint, SubFileInfo)>();
60+
for(int file_ind = 0; file_ind < FilesInfo.Count; file_ind++)
61+
{
62+
var file_info = FilesInfo[file_ind];
63+
if (file_info.SubDirsInfoStartIndex.HasValue)
64+
{
65+
// This is a sub-directory, TO-DO: Extract all files in the sub-directory
66+
for (int i = 0; i < file_info.SubDirsCount; i++)
67+
{
68+
var sub_dir_info = SubDirsInfo[file_info.SubDirsInfoStartIndex.Value + i];
69+
70+
var dir_path = Path.Combine(extract_path, sub_dir_info.DirId.ToString());
71+
if (!Directory.Exists(dir_path))
72+
{
73+
Directory.CreateDirectory(dir_path);
74+
}
75+
76+
var start_block = file_info.StartBlock + sub_dir_info.StartBlock;
77+
var mem_stream = IsoUtility.GetSectors(iso_stream, start_block, (int)sub_dir_info.FilesListBlockCount);
78+
if (mem_stream.Length == 0)
79+
{
80+
// Skip to next file
81+
continue;
82+
}
83+
int j = 0;
84+
85+
while (mem_stream.ReadByte() > 0)
86+
{
87+
SubFileInfo sub_file_info = BinaryMapping.ReadObject<SubFileInfo>(mem_stream.SetPosition(j * 48));
88+
if (sub_file_info.DirId == sub_dir_info.DirId)
89+
{
90+
var file_path = Path.Combine(dir_path, sub_file_info.Filename);
91+
var mem_stream_2 = IsoUtility.GetSectors(iso_stream, start_block + sub_file_info.StartBlock, (int)sub_file_info.BlockCount);
92+
byte[] buffer = sub_file_info.IsCompressed == 1 ? Decompress(mem_stream_2) : mem_stream_2.ReadBytes((int)sub_file_info.BlockCount * 0x800);
93+
var file_stream = File.Open(file_path, FileMode.Create, FileAccess.Write);
94+
file_stream.Write(buffer, 0, (int)sub_file_info.FileLen);
95+
file_stream.Close();
96+
}
97+
else if (sub_file_info.SymlinkDirId == sub_dir_info.DirId)
98+
{
99+
// This dir contains a symlink to a file in another dir
100+
// TO-DO: Decide if we want a symlink to the true location of the file
101+
var symlink_path = Path.Combine(dir_path, sub_file_info.Filename);
102+
var target_path = Path.Combine(extract_path, sub_file_info.DirId.ToString(), sub_file_info.Filename);
103+
symlinks.Add((symlink_path, target_path, dir_path, start_block, sub_file_info));
104+
}
105+
//*
106+
else
107+
{
108+
// Both dir ID's are for different dirs?
109+
var symlink_path = Path.Combine(dir_path, sub_file_info.Filename);
110+
var target_path = Path.Combine(extract_path, sub_file_info.SymlinkDirId.ToString(), sub_file_info.Filename);
111+
symlinks.Add((symlink_path, target_path, dir_path, start_block, sub_file_info));
112+
}
113+
//*/
114+
j++;
115+
}
116+
}
117+
}
118+
else
119+
{
120+
// This is a file, extract it
121+
var file_path = Path.Combine(extract_path, file_info.Filename);
122+
var mem_stream = IsoUtility.GetSectors(iso_stream, file_info.StartBlock, (int)file_info.BlockCount);
123+
var file_stream = File.Open(file_path, FileMode.Create, FileAccess.Write);
124+
file_stream.Write(mem_stream.ReadBytes((int)file_info.BlockCount * 0x800));
125+
file_stream.Close();
126+
}
127+
if (onProgress != null)
128+
{
129+
onProgress(file_ind / (float)FilesInfo.Count);
130+
}
131+
}
132+
133+
foreach ((String symlink_path, String target_path, String dir_path, uint start_block, SubFileInfo sub_file_info) in symlinks)
134+
{
135+
// Create the symbolic link
136+
if (File.Exists(target_path))
137+
{
138+
if (!File.Exists(symlink_path))
139+
{
140+
try
141+
{
142+
File.CreateSymbolicLink(symlink_path, target_path);
143+
}
144+
catch (System.IO.IOException)
145+
{
146+
// Fallback in case we don't have permissions for a symlink
147+
File.Copy(target_path, symlink_path, overwrite: false);
148+
}
149+
}
150+
}
151+
else
152+
{
153+
var file_path = Path.Combine(dir_path, sub_file_info.Filename);
154+
var mem_stream_2 = IsoUtility.GetSectors(iso_stream, start_block + sub_file_info.StartBlock, (int)sub_file_info.BlockCount);
155+
byte[] buffer = sub_file_info.IsCompressed == 1 ? Decompress(mem_stream_2) : mem_stream_2.ReadBytes((int)sub_file_info.BlockCount * 0x800);
156+
var file_stream = File.Open(file_path, FileMode.Create, FileAccess.Write);
157+
file_stream.Write(buffer, 0, (int)sub_file_info.FileLen);
158+
file_stream.Close();
159+
}
160+
}
161+
162+
if (onProgress != null)
163+
{
164+
onProgress(1.0f);
165+
}
166+
}
167+
168+
public static byte[] Decompress(Stream src)
169+
{
170+
return Decompress(new BinaryReader(src).ReadBytes((int)src.Length));
171+
}
172+
173+
// Based on Ghidra decompilation
174+
public static byte[] Decompress(byte[] srcData)
175+
{
176+
List<byte> dstData = new List<byte>();
177+
byte[] tmpBuffer = new byte[256];
178+
int numIters = srcData.Length / 0x1000;
179+
numIters += srcData.Length % 0x1000 != 0 ? 1 : 0;
180+
181+
for (uint i = 0; i < numIters; i++)
182+
{
183+
uint srcInd = i * 0x1000;
184+
185+
uint uVar6 = 0;
186+
uint uVar7 = 0;
187+
uint uVar4 = 0;
188+
uint uVar8 = 1;
189+
uint uVar5;
190+
int iVar3;
191+
uint uVar10;
192+
uint uVar9;
193+
uint uVar11;
194+
while (true)
195+
{
196+
while (true)
197+
{
198+
if (uVar6 == 0)
199+
{
200+
uVar6 = 255;
201+
uVar4 = 8;
202+
uVar7 = srcData[srcInd];
203+
srcInd++;
204+
}
205+
uVar5 = uVar6 / 2;
206+
iVar3 = (int)(uVar4 - 1);
207+
if ((uVar6 & uVar7 & (uVar5 ^ 255)) == 0)
208+
break;
209+
if (uVar5 == 0)
210+
{
211+
uVar5 = 255;
212+
iVar3 = 8;
213+
uVar7 = srcData[srcInd];
214+
srcInd++;
215+
}
216+
uVar5 = uVar5 & uVar7;
217+
uVar10 = (uint)(8 - iVar3);
218+
uVar6 = 255;
219+
uVar4 = 8;
220+
uVar7 = (uint)srcData[srcInd];
221+
srcInd++;
222+
if (uVar10 != 0)
223+
{
224+
uVar6 = (uint)(255 >> (int)(uVar10 & 31));
225+
uVar4 = 8 - uVar10;
226+
uVar5 = uVar5 << (int)(uVar10 & 31) | ((uVar6 ^ 255) & uVar7) >> (int)(uVar4 & 0x1f);
227+
228+
}
229+
tmpBuffer[uVar8] = (byte)uVar5;
230+
dstData.Add((byte)uVar5);
231+
uVar8++;
232+
uVar8 &= 255;
233+
}
234+
if (uVar5 == 0)
235+
{
236+
uVar5 = 255;
237+
iVar3 = 8;
238+
uVar7 = srcData[srcInd];
239+
srcInd++;
240+
}
241+
uVar5 = uVar5 & uVar7;
242+
uVar6 = (uint)(8 - iVar3);
243+
uVar10 = 255;
244+
uVar4 = 8;
245+
uVar7 = srcData[srcInd];
246+
if (uVar6 != 0)
247+
{
248+
uVar10 = (uint)(255 >> (int)(uVar6 & 31));
249+
uVar4 = 8 - uVar6;
250+
uVar5 = uVar5 << (int)(uVar6 & 31) | ((uVar10 ^ 255) & uVar7) >> (int)(uVar4 & 31);
251+
}
252+
if (uVar5 == 0)
253+
break;
254+
uVar9 = 0;
255+
uint tmpSrcInd = srcInd + 1;
256+
if (uVar10 == 0)
257+
{
258+
uVar10 = 255;
259+
uVar4 = 8;
260+
uVar7 = srcData[srcInd + 1];
261+
tmpSrcInd = srcInd + 2;
262+
}
263+
srcInd = tmpSrcInd;
264+
uVar6 = 4 - uVar4;
265+
uVar11 = 4;
266+
if (0 < (int)uVar6)
267+
{
268+
uVar9 = uVar10 & uVar7;
269+
uVar10 = 255;
270+
uVar4 = 8;
271+
uVar7 = srcData[srcInd];
272+
srcInd++;
273+
uVar11 = uVar6;
274+
}
275+
uVar6 = uVar10 >> (int)(uVar11 & 31);
276+
uVar4 = uVar4 - uVar11;
277+
iVar3 = (int)(uVar9 << (int)(uVar11 & 31) | (uVar10 & uVar7 & (uVar6 ^ 255)) >> (int)(uVar4 & 31)) + 1;
278+
do
279+
{
280+
byte bVar1 = tmpBuffer[uVar5 & 255];
281+
tmpBuffer[uVar8] = bVar1;
282+
dstData.Add(bVar1);
283+
uVar8++;
284+
uVar8 &= 255;
285+
uVar5++;
286+
iVar3--;
287+
} while (-1 < iVar3);
288+
}
289+
}
290+
return dstData.ToArray();
291+
}
292+
}
293+
294+
public record RootFileInfo
295+
{
296+
[Data(Count = 16)] public string Filename { get; set; } = "";
297+
[Data] public uint StartBlock { get; set; }
298+
[Data] public uint BlockCount { get; set; }
299+
[Data] public uint SubDirsInfoOffset { get; set; }
300+
[Data] public uint SubDirsCount { get; set; }
301+
public int? SubDirsInfoStartIndex = null;
302+
}
303+
304+
public record SubDirInfo
305+
{
306+
[Data] public uint DirId { get; set; }
307+
[Data] public uint StartBlock { get; set; }
308+
[Data] public uint FilesDataBlockCount { get; set; }
309+
[Data] public byte FilesListBlockCount { get; set; }
310+
[Data] public byte Unk0x1D { get; set; }
311+
[Data] public byte Unk0x1E { get; set; }
312+
[Data] public byte Unk0x1F { get; set; }
313+
}
314+
315+
public record SubFileInfo
316+
{
317+
[Data(Count = 24)] public string Filename { get; set; } = "";
318+
[Data] public uint FileLen { get; set; }
319+
[Data] public uint DirId { get; set; }
320+
[Data] public uint SymlinkDirId { get; set; }
321+
[Data] public uint BlockCount { get; set; }
322+
[Data] public uint StartBlock { get; set; }
323+
[Data] public byte FileType { get; set; }
324+
[Data] public byte IsCompressed { get; set; }
325+
[Data] public byte Unk0x2E { get; set; }
326+
[Data] public byte Unk0x2F { get; set; }
327+
}
328+
329+
public class Dat
330+
{
331+
public List<SubDirInfo> SubDirsInfo { get; set; } = new List<SubDirInfo>();
332+
public List<List<SubFileInfo>> SubDirsFileLists { get; set; } = new List<List<SubFileInfo>>();
333+
public List<List<byte[]>> SubDirsFileContents { get; set; } = new List<List<byte[]>>();
334+
}
335+
}

OpenKh.Recom/OpenKh.Recom.csproj

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Xe.BinaryMapper" Version="1.5.2" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\OpenKh.Common\OpenKh.Common.csproj" />
15+
<ProjectReference Include="..\XeEngine.Tools.Public\Xe\Xe.csproj" />
16+
</ItemGroup>
17+
18+
</Project>

0 commit comments

Comments
 (0)