forked from TASEmulators/BizHawk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFileID.cs
633 lines (527 loc) · 22.9 KB
/
FileID.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
using System;
using System.IO;
using System.Collections.Generic;
using BizHawk.Common;
//HOW TO USE
//we dont expect anyone to use this fully yet. It's just over-engineered for future use.
//for now, just use it when you truly dont know what to do with a file.
//This system depends heavily on the provided extension. We're not going to exhaustively try every format all the time. If someone loads a cue which is named .sfc, we cant cope with that.
//However, common mistakes will be handled, on an as-needed basis.
//TODO - check for archives too? further, check archive contents (probably just based on filename)?
//TODO - parameter to enable checks vs firmware, game databases
//TODO (in client) - costly hashes could happen only once the file type is known (and a hash for that filetype could be used)
namespace BizHawk.Emulation.Cores
{
/// <summary>
/// Each of these should ideally represent a single file type.
/// However for now they just may resemble a console, and a core would know how to parse some set of those after making its own determination.
/// If formats are very similar but with small differences, and that determination can be made, then it will be in the ExtraInfo in the FileIDResult
/// </summary>
public enum FileIDType
{
None,
Multiple, //dont think this makes sense. shouldnt the multiple options be returned?
Disc, //an unknown disc
PSX, PSX_EXE, PSF,
PSP,
Saturn, MegaCD,
PCE, SGX, TurboCD,
INES, FDS, UNIF, NSF,
SFC, N64,
GB, GBC, GBA, NDS,
COL,
SG, SMS, GG, S32X,
SMD, //http://en.wikibooks.org/wiki/Genesis_Programming#ROM_header
WS, WSC, NGC,
C64,
INT,
A26, A52, A78, LNX,
JAD, SBI,
M3U,
//audio codec formats
WAV, APE, MPC, FLAC,
MP3, //can't be ID'd very readily..
//misc disc-related files:
ECM
}
public class FileIDResult
{
public FileIDResult()
{
}
public FileIDResult(FileIDType type, int confidence)
{
FileIDType = type;
Confidence = confidence;
}
public FileIDResult(FileIDType type)
{
FileIDType = type;
}
/// <summary>
/// a percentage between 0 and 100 assessing the confidence of this result
/// </summary>
public int Confidence;
/// <summary>
///
/// </summary>
public FileIDType FileIDType;
/// <summary>
/// extra information which could be easily gotten during the file ID (region, suspected homebrew, CRC invalid, etc.)
/// </summary>
public Dictionary<string, object> ExtraInfo = new Dictionary<string, object>();
}
public class FileIDResults : List<FileIDResult>
{
public FileIDResults() { }
public FileIDResults(FileIDResult item)
{
base.Add(item);
}
public new void Sort()
{
base.Sort((x, y) => x.Confidence.CompareTo(y.Confidence));
}
/// <summary>
/// indicates whether the client should try again after mounting the disc image for further inspection
/// </summary>
public bool ShouldTryDisc;
}
public class FileID
{
/// <summary>
/// parameters for an Identify job
/// </summary>
public class IdentifyParams
{
/// <summary>
/// The extension of the original file (with or without the .)
/// </summary>
public string Extension;
/// <summary>
/// a seekable stream which can be used
/// </summary>
public Stream SeekableStream;
/// <summary>
/// the file in question mounted as a disc
/// </summary>
public DiscSystem.Disc Disc;
}
class IdentifyJob
{
public Stream Stream;
public string Extension;
public DiscSystem.Disc Disc;
}
/// <summary>
/// performs wise heuristics to identify a file.
/// this will attempt to return early if a confident result can be produced.
/// </summary>
public FileIDResults Identify(IdentifyParams p)
{
IdentifyJob job = new IdentifyJob() {
Stream = p.SeekableStream,
Disc = p.Disc
};
//if we have a disc, that's a separate codepath
if (job.Disc != null)
return IdentifyDisc(job);
FileIDResults ret = new FileIDResults();
string ext = p.Extension;
if(ext != null)
{
ext = ext.TrimStart('.').ToUpper();
job.Extension = ext;
}
if (job.Extension == "CUE")
{
ret.ShouldTryDisc = true;
return ret;
}
if(job.Extension != null)
{
//first test everything associated with this extension
ExtensionInfo handler = null;
if (ExtensionHandlers.TryGetValue(ext, out handler))
{
foreach (var del in handler.Testers)
{
var fidr = del(job);
if (fidr.FileIDType == FileIDType.None)
continue;
ret.Add(fidr);
}
ret.Sort();
//add a low confidence result just based on extension, if it doesnt exist
if(ret.Find( (x) => x.FileIDType == handler.DefaultForExtension) == null)
{
var fidr = new FileIDResult(handler.DefaultForExtension, 5);
ret.Add(fidr);
}
}
}
ret.Sort();
//if we didnt find anything high confidence, try all the testers (TODO)
return ret;
}
/// <summary>
/// performs wise heuristics to identify a file (simple version)
/// </summary>
public FileIDType IdentifySimple(IdentifyParams p)
{
var ret = Identify(p);
if (ret.ShouldTryDisc)
return FileIDType.Disc;
if (ret.Count == 0)
return FileIDType.None;
else if(ret.Count == 1)
return ret[0].FileIDType;
else if (ret[0].Confidence == ret[1].Confidence)
return FileIDType.Multiple;
else return ret[0].FileIDType;
}
FileIDResults IdentifyDisc(IdentifyJob job)
{
//DiscSystem could use some newer approaches from this file (instead of parsing ISO filesystem... maybe?)
switch (job.Disc.DetectDiscType())
{
case DiscSystem.DiscType.SegaSaturn:
return new FileIDResults(new FileIDResult(FileIDType.Saturn, 100));
case DiscSystem.DiscType.SonyPSP:
return new FileIDResults(new FileIDResult(FileIDType.PSP, 100));
case DiscSystem.DiscType.SonyPSX:
return new FileIDResults(new FileIDResult(FileIDType.PSX, 100));
case DiscSystem.DiscType.MegaCD:
return new FileIDResults(new FileIDResult(FileIDType.MegaCD, 100));
case DiscSystem.DiscType.TurboCD:
return new FileIDResults(new FileIDResult(FileIDType.TurboCD, 5));
case DiscSystem.DiscType.UnknownCDFS:
case DiscSystem.DiscType.UnknownFormat:
default:
return new FileIDResults(new FileIDResult());
}
}
class SimpleMagicRecord
{
public int Offset;
public string Key;
public int Length = -1;
public Func<Stream,bool> ExtraCheck;
}
//some of these (NES, UNIF for instance) should be lower confidence probably...
//if you change some of the Length arguments for longer keys, please make notes about why
static class SimpleMagics
{
public static SimpleMagicRecord INES = new SimpleMagicRecord { Offset = 0, Key = "NES" };
public static SimpleMagicRecord UNIF = new SimpleMagicRecord { Offset = 0, Key = "UNIF" };
public static SimpleMagicRecord NSF = new SimpleMagicRecord { Offset = 0, Key = "NESM\x1A" };
public static SimpleMagicRecord FDS_HEADERLESS = new SimpleMagicRecord { Offset = 0, Key = "\x01*NINTENDO-HVC*" };
public static SimpleMagicRecord FDS_HEADER = new SimpleMagicRecord { Offset = 0, Key = "FDS\x1A" };
//the GBA nintendo logo.. we'll only use 16 bytes of it but theyre all here, for reference
//we cant expect these roms to be normally sized, but we may be able to find other features of the header to use for extra checks
public static SimpleMagicRecord GBA = new SimpleMagicRecord { Offset = 4, Length = 16, Key = "\x24\xFF\xAE\x51\x69\x9A\xA2\x21\x3D\x84\x82\x0A\x84\xE4\x09\xAD\x11\x24\x8B\x98\xC0\x81\x7F\x21\xA3\x52\xBE\x19\x93\x09\xCE\x20\x10\x46\x4A\x4A\xF8\x27\x31\xEC\x58\xC7\xE8\x33\x82\xE3\xCE\xBF\x85\xF4\xDF\x94\xCE\x4B\x09\xC1\x94\x56\x8A\xC0\x13\x72\xA7\xFC\x9F\x84\x4D\x73\xA3\xCA\x9A\x61\x58\x97\xA3\x27\xFC\x03\x98\x76\x23\x1D\xC7\x61\x03\x04\xAE\x56\xBF\x38\x84\x00\x40\xA7\x0E\xFD\xFF\x52\xFE\x03\x6F\x95\x30\xF1\x97\xFB\xC0\x85\x60\xD6\x80\x25\xA9\x63\xBE\x03\x01\x4E\x38\xE2\xF9\xA2\x34\xFF\xBB\x3E\x03\x44\x78\x00\x90\xCB\x88\x11\x3A\x94\x65\xC0\x7C\x63\x87\xF0\x3C\xAF\xD6\x25\xE4\x8B\x38\x0A\xAC\x72\x21\xD4\xF8\x07" };
public static SimpleMagicRecord NDS = new SimpleMagicRecord { Offset = 0xC0, Length = 16, Key = "\x24\xFF\xAE\x51\x69\x9A\xA2\x21\x3D\x84\x82\x0A\x84\xE4\x09\xAD\x11\x24\x8B\x98\xC0\x81\x7F\x21\xA3\x52\xBE\x19\x93\x09\xCE\x20\x10\x46\x4A\x4A\xF8\x27\x31\xEC\x58\xC7\xE8\x33\x82\xE3\xCE\xBF\x85\xF4\xDF\x94\xCE\x4B\x09\xC1\x94\x56\x8A\xC0\x13\x72\xA7\xFC\x9F\x84\x4D\x73\xA3\xCA\x9A\x61\x58\x97\xA3\x27\xFC\x03\x98\x76\x23\x1D\xC7\x61\x03\x04\xAE\x56\xBF\x38\x84\x00\x40\xA7\x0E\xFD\xFF\x52\xFE\x03\x6F\x95\x30\xF1\x97\xFB\xC0\x85\x60\xD6\x80\x25\xA9\x63\xBE\x03\x01\x4E\x38\xE2\xF9\xA2\x34\xFF\xBB\x3E\x03\x44\x78\x00\x90\xCB\x88\x11\x3A\x94\x65\xC0\x7C\x63\x87\xF0\x3C\xAF\xD6\x25\xE4\x8B\x38\x0A\xAC\x72\x21\xD4\xF8\x07" };
public static SimpleMagicRecord GB = new SimpleMagicRecord { Offset=0x104, Length = 16, Key = "\xCE\xED\x66\x66\xCC\x0D\x00\x0B\x03\x73\x00\x83\x00\x0C\x00\x0D\x00\x08\x11\x1F\x88\x89\x00\x0E\xDC\xCC\x6E\xE6\xDD\xDD\xD9\x99\xBB\xBB\x67\x63\x6E\x0E\xEC\xCC\xDD\xDC\x99\x9F\xBB\xB9\x33\x3E" };
public static SimpleMagicRecord S32X = new SimpleMagicRecord { Offset = 0x100, Key = "SEGA 32X" };
public static SimpleMagicRecord SEGAGENESIS = new SimpleMagicRecord { Offset = 0x100, Key = "SEGA GENESIS" };
public static SimpleMagicRecord SEGAMEGADRIVE = new SimpleMagicRecord { Offset = 0x100, Key = "SEGA MEGA DRIVE" };
public static SimpleMagicRecord SEGASATURN = new SimpleMagicRecord { Offset = 0, Key = "SEGA SEGASATURN" };
public static SimpleMagicRecord SEGADISCSYSTEM = new SimpleMagicRecord { Offset = 0, Key = "SEGADISCSYSTEM" };
public static SimpleMagicRecord PSX = new SimpleMagicRecord { Offset = 0x24E0, Key = " Licensed by Sony Computer Entertainment" }; //there might be other ideas for checking in mednafen sources, if we need them
public static SimpleMagicRecord PSX_EXE = new SimpleMagicRecord { Key = "PS-X EXE\0" };
public static SimpleMagicRecord PSP = new SimpleMagicRecord { Offset = 0x8000, Key = "\x01CD001\x01\0x00PSP GAME" };
//https://sites.google.com/site/atari7800wiki/a78-header
public static SimpleMagicRecord A78 = new SimpleMagicRecord { Offset = 0, Key = "\x01ATARI7800" };
//could be at various offsets?
public static SimpleMagicRecord TMR_SEGA = new SimpleMagicRecord { Offset = 0x7FF0, Key = "TMR SEGA" };
public static SimpleMagicRecord SBI = new SimpleMagicRecord { Key = "SBI\0" };
public static SimpleMagicRecord M3U = new SimpleMagicRecord { Key = "#EXTM3U" }; //note: M3U may not have this. EXTM3U only has it. We'll still catch it by extension though.
public static SimpleMagicRecord ECM = new SimpleMagicRecord { Key = "ECM\0" };
public static SimpleMagicRecord FLAC = new SimpleMagicRecord { Key = "fLaC" };
public static SimpleMagicRecord MPC = new SimpleMagicRecord { Key = "MP+", ExtraCheck = (s) => { s.Position += 3; return s.ReadByte() >= 7; } };
public static SimpleMagicRecord APE = new SimpleMagicRecord { Key = "MAC " };
public static SimpleMagicRecord[] WAV = new SimpleMagicRecord[] {
new SimpleMagicRecord { Offset = 0, Key = "RIFF" },
new SimpleMagicRecord { Offset = 8, Key = "WAVEfmt " }
};
}
class ExtensionInfo
{
public ExtensionInfo(FileIDType defaultForExtension, FormatTester tester)
{
Testers = new List<FormatTester>(1);
if(tester != null)
Testers.Add(tester);
DefaultForExtension = defaultForExtension;
}
public FileIDType DefaultForExtension;
public List<FormatTester> Testers;
}
/// <summary>
/// testers to try for each extension, along with a default for the extension
/// </summary>
static Dictionary<string, ExtensionInfo> ExtensionHandlers = new Dictionary<string, ExtensionInfo> {
{ "NES", new ExtensionInfo(FileIDType.INES, Test_INES ) },
{ "FDS", new ExtensionInfo(FileIDType.FDS, Test_FDS ) },
{ "GBA", new ExtensionInfo(FileIDType.GBA, (j)=>Test_Simple(j,FileIDType.GBA,SimpleMagics.GBA) ) },
{ "NDS", new ExtensionInfo(FileIDType.NDS, (j)=>Test_Simple(j,FileIDType.NDS,SimpleMagics.NDS) ) },
{ "UNF", new ExtensionInfo(FileIDType.UNIF, Test_UNIF ) },
{ "UNIF", new ExtensionInfo(FileIDType.UNIF, Test_UNIF ) },
{ "GB", new ExtensionInfo(FileIDType.GB, Test_GB_GBC ) },
{ "GBC", new ExtensionInfo(FileIDType.GBC, Test_GB_GBC ) },
{ "N64", new ExtensionInfo(FileIDType.N64, Test_N64 ) },
{ "Z64", new ExtensionInfo(FileIDType.N64, Test_N64 ) },
{ "V64", new ExtensionInfo(FileIDType.N64, Test_N64 ) },
{ "A78", new ExtensionInfo(FileIDType.A78, Test_A78 ) },
{ "SMS", new ExtensionInfo(FileIDType.SMS, Test_SMS ) },
{ "BIN", new ExtensionInfo(FileIDType.Multiple, Test_BIN_ISO ) },
{ "ISO", new ExtensionInfo(FileIDType.Multiple, Test_BIN_ISO ) },
{ "M3U", new ExtensionInfo(FileIDType.M3U, (j)=>Test_Simple(j,FileIDType.M3U,SimpleMagics.M3U) ) },
{ "JAD", new ExtensionInfo(FileIDType.Multiple, Test_JAD_JAC ) },
{ "JAC", new ExtensionInfo(FileIDType.Multiple, Test_JAD_JAC ) },
{ "SBI", new ExtensionInfo(FileIDType.SBI, (j)=>Test_Simple(j,FileIDType.SBI,SimpleMagics.SBI) ) },
{ "EXE", new ExtensionInfo(FileIDType.PSX_EXE, (j)=>Test_Simple(j,FileIDType.PSX_EXE,SimpleMagics.PSX_EXE) ) },
//royal mess
{ "MD", new ExtensionInfo(FileIDType.SMD, null ) },
{ "SMD", new ExtensionInfo(FileIDType.SMD, null ) },
{ "GEN", new ExtensionInfo(FileIDType.SMD, null ) },
//nothing yet...
{ "PSF", new ExtensionInfo(FileIDType.PSF, null) },
{ "INT", new ExtensionInfo(FileIDType.INT, null) },
{ "SFC", new ExtensionInfo(FileIDType.SFC, null) },
{ "SMC", new ExtensionInfo(FileIDType.SFC, null) },
{ "LNX", new ExtensionInfo(FileIDType.LNX, null ) },
{ "SG", new ExtensionInfo(FileIDType.SG, null ) },
{ "SGX", new ExtensionInfo(FileIDType.SGX, null ) },
{ "COL", new ExtensionInfo(FileIDType.COL, null ) },
{ "A52", new ExtensionInfo(FileIDType.A52, null ) },
{ "A26", new ExtensionInfo(FileIDType.A26, null ) },
{ "PCE", new ExtensionInfo(FileIDType.PCE, null ) },
{ "GG", new ExtensionInfo(FileIDType.GG, null ) },
{ "WS", new ExtensionInfo(FileIDType.WS, null ) },
{ "WSC", new ExtensionInfo(FileIDType.WSC, null ) },
{ "NGC", new ExtensionInfo(FileIDType.NGC, null ) },
{ "32X", new ExtensionInfo(FileIDType.S32X, (j)=>Test_Simple(j,FileIDType.S32X,SimpleMagics.S32X) ) },
//various C64 formats.. can we distinguish between these?
{ "PRG", new ExtensionInfo(FileIDType.C64, null ) },
{ "D64", new ExtensionInfo(FileIDType.C64, null ) },
{ "T64", new ExtensionInfo(FileIDType.C64, null ) },
{ "G64", new ExtensionInfo(FileIDType.C64, null ) },
{ "CRT", new ExtensionInfo(FileIDType.C64, null ) },
{ "NIB", new ExtensionInfo(FileIDType.C64, null ) }, //not supported yet
//for now
{ "ROM", new ExtensionInfo(FileIDType.Multiple, null ) }, //could be MSX too
{ "MP3", new ExtensionInfo(FileIDType.MP3, null ) },
{ "WAV", new ExtensionInfo(FileIDType.WAV, (j)=>Test_Simple(j,FileIDType.WAV,SimpleMagics.WAV) ) },
{ "APE", new ExtensionInfo(FileIDType.APE, (j)=>Test_Simple(j,FileIDType.APE,SimpleMagics.APE) ) },
{ "MPC", new ExtensionInfo(FileIDType.MPC, (j)=>Test_Simple(j,FileIDType.MPC,SimpleMagics.MPC) ) },
{ "FLAC", new ExtensionInfo(FileIDType.FLAC, (j)=>Test_Simple(j,FileIDType.FLAC,SimpleMagics.FLAC) ) },
{ "ECM", new ExtensionInfo(FileIDType.ECM, (j)=>Test_Simple(j,FileIDType.ECM,SimpleMagics.ECM) ) },
};
delegate FileIDResult FormatTester(IdentifyJob job);
static int[] no_offsets = new int[] { 0 };
/// <summary>
/// checks for the magic string (bytewise ASCII check) at the given address
/// </summary>
static bool CheckMagic(Stream stream, IEnumerable<SimpleMagicRecord> recs, params int[] offsets)
{
if (offsets.Length == 0)
offsets = no_offsets;
foreach (int n in offsets)
{
bool ok = true;
foreach (var r in recs)
{
if (!CheckMagicOne(stream, r, n))
{
ok = false;
break;
}
}
if (ok) return true;
}
return false;
}
static bool CheckMagic(Stream stream, SimpleMagicRecord rec, params int[] offsets)
{
return CheckMagic(stream, new SimpleMagicRecord[] { rec }, offsets);
}
static bool CheckMagicOne(Stream stream, SimpleMagicRecord rec, int offset)
{
stream.Position = rec.Offset + offset;
string key = rec.Key;
int len = rec.Length;
if (len == -1)
len = key.Length;
for (int i = 0; i < len; i++)
{
int n = stream.ReadByte();
if (n == -1) return false;
if (n != key[i])
return false;
}
if (rec.ExtraCheck != null)
{
stream.Position = rec.Offset + offset;
return rec.ExtraCheck(stream);
}
else return true;
}
static int ReadByte(Stream stream, int ofs)
{
stream.Position = ofs;
return stream.ReadByte();
}
static FileIDResult Test_INES(IdentifyJob job)
{
if (!CheckMagic(job.Stream, SimpleMagics.INES))
return new FileIDResult();
var ret = new FileIDResult(FileIDType.INES, 100);
//an INES file should be a multiple of 8k, with the 16 byte header.
//if it isnt.. this is fishy.
if (((job.Stream.Length - 16) & (8 * 1024 - 1)) != 0)
ret.Confidence = 50;
return ret;
}
static FileIDResult Test_FDS(IdentifyJob job)
{
if (CheckMagic(job.Stream, SimpleMagics.FDS_HEADER))
return new FileIDResult(FileIDType.FDS, 90); //kind of a small header..
if (CheckMagic(job.Stream, SimpleMagics.FDS_HEADERLESS))
return new FileIDResult(FileIDType.FDS, 95);
return new FileIDResult();
}
/// <summary>
/// all magics must pass
/// </summary>
static FileIDResult Test_Simple(IdentifyJob job, FileIDType type, SimpleMagicRecord[] magics)
{
var ret = new FileIDResult(type);
if (CheckMagic(job.Stream, magics))
return new FileIDResult(type, 100);
else
return new FileIDResult();
}
static FileIDResult Test_Simple(IdentifyJob job, FileIDType type, SimpleMagicRecord magic)
{
var ret = new FileIDResult(type);
if (CheckMagic(job.Stream, magic))
return new FileIDResult(type, 100);
else
return new FileIDResult();
}
static FileIDResult Test_UNIF(IdentifyJob job)
{
if (!CheckMagic(job.Stream, SimpleMagics.UNIF))
return new FileIDResult();
//TODO - simple parser (for starters, check for a known chunk being next, see http://wiki.nesdev.com/w/index.php/UNIF)
var ret = new FileIDResult(FileIDType.UNIF, 100);
return ret;
}
static FileIDResult Test_GB_GBC(IdentifyJob job)
{
if (!CheckMagic(job.Stream, SimpleMagics.GB))
return new FileIDResult();
var ret = new FileIDResult(FileIDType.GB, 100);
int type = ReadByte(job.Stream, 0x143);
if ((type & 0x80) != 0)
ret.FileIDType = FileIDType.GBC;
//could check cart type and rom size for extra info if necessary
return ret;
}
static FileIDResult Test_SMS(IdentifyJob job)
{
//http://www.smspower.org/Development/ROMHeader
//actually, not sure how to handle this yet
return new FileIDResult();
}
static FileIDResult Test_N64(IdentifyJob job)
{
// .Z64 = No swapping
// .N64 = Word Swapped
// .V64 = Byte Swapped
//not sure how to check for these yet...
var ret = new FileIDResult(FileIDType.N64, 5);
if (job.Extension == "V64") ret.ExtraInfo["byteswap"] = true;
if (job.Extension == "N64") ret.ExtraInfo["wordswap"] = true;
return ret;
}
static FileIDResult Test_A78(IdentifyJob job)
{
int len = (int)job.Stream.Length;
//we may have a header to analyze
if (len % 1024 == 128)
{
if (CheckMagic(job.Stream, SimpleMagics.A78))
new FileIDResult(FileIDType.A78, 100);
}
else if (len % 1024 == 0)
{
}
else { }
return new FileIDResult(0);
}
static FileIDResult Test_BIN_ISO(IdentifyJob job)
{
//ok, this is complicated.
//there are lots of mislabeled bins/isos so lets just treat them the same (mostly)
//if the BIN cant be recognized, but it is small, it is more likely some other rom BIN than a disc (turbocd or other)
if (job.Extension == "BIN")
{
//first we can check for SMD magic words.
//since this extension is ambiguous, we can't be completely sure about it. but it's almost surely accurate
if (CheckMagic(job.Stream, SimpleMagics.SEGAGENESIS))
{
var ret = new FileIDResult(FileIDType.SMD, 95);
ret.ExtraInfo["type"] = "genesis";
return ret;
}
if (CheckMagic(job.Stream, SimpleMagics.SEGAMEGADRIVE))
{
var ret = new FileIDResult(FileIDType.SMD, 95);
ret.ExtraInfo["type"] = "megadrive";
}
}
//well... guess it's a disc.
//since it's just a bin, we dont need the user to provide a DiscSystem disc.
//lets just analyze this as best we can.
//for PSX, we have a magic word to look for.
//it's at 0x24E0 with a mode2 (2352 byte) track 1.
//what if its 2048 byte?
//i found a ".iso" which was actually 2352 byte sectors..
//found a hilarious ".bin.iso" which was actually 2352 byte sectors
//so, I think it's possible that every valid PSX disc is mode2 in the track 1
if (CheckMagic(job.Stream, SimpleMagics.PSX))
{
var ret = new FileIDResult(FileIDType.PSX, 95);
//this is an unreliable way to get a PSX game!
ret.ExtraInfo["unreliable"] = true;
return ret;
}
//it's not proven that this is reliable. this is actually part of the mode1 CDFS header. perhaps it's mobile?
//if it's mobile, we'll need to mount it as an ISO file here via discsystem
if (CheckMagic(job.Stream, SimpleMagics.PSP))
return new FileIDResult(FileIDType.PSP, 95);
//if this was an ISO, we might discover the magic word at offset 0...
//if it was a mode2/2352 bin, we might discover it at offset 16 (after the sync)
if(CheckMagic(job.Stream, SimpleMagics.SEGASATURN,0))
return new FileIDResult(FileIDType.Saturn, job.Extension == "ISO" ? 95 : 90);
if (CheckMagic(job.Stream, SimpleMagics.SEGASATURN, 16))
return new FileIDResult(FileIDType.Saturn, job.Extension == "BIN" ? 95 : 90);
if (CheckMagic(job.Stream, SimpleMagics.SEGADISCSYSTEM, 0))
return new FileIDResult(FileIDType.MegaCD, job.Extension == "ISO" ? 95 : 90);
if (CheckMagic(job.Stream, SimpleMagics.SEGADISCSYSTEM, 16))
return new FileIDResult(FileIDType.MegaCD, job.Extension == "BIN" ? 95 : 90);
if (job.Extension == "ISO")
return new FileIDResult(FileIDType.Disc, 1);
else
return new FileIDResult(FileIDType.Multiple, 1);
}
static FileIDResult Test_JAD_JAC(IdentifyJob job)
{
//TBD
//just mount it as a disc and send it through the disc checker?
return null;
}
}
}