mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 19:24:25 +00:00
534 lines
19 KiB
C#
534 lines
19 KiB
C#
// /***************************************************************************
|
|
// The Disc Image Chef
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Filename : Alcohol120.cs
|
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
|
//
|
|
// Component : Core algorithms.
|
|
//
|
|
// --[ Description ] ----------------------------------------------------------
|
|
//
|
|
// Contains code for creating Alcohol 120% disc images.
|
|
//
|
|
// --[ License ] --------------------------------------------------------------
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
// Copyright © 2011-2018 Natalia Portillo
|
|
// ****************************************************************************/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using DiscImageChef.CommonTypes;
|
|
using DiscImageChef.DiscImages;
|
|
|
|
namespace DiscImageChef.Core.Devices.Dumping
|
|
{
|
|
// TODO: For >4.0, this class must disappear
|
|
class Alcohol120
|
|
{
|
|
byte[] bca;
|
|
byte[] dmi;
|
|
string extension;
|
|
AlcoholFooter footer;
|
|
AlcoholHeader header;
|
|
string outputPrefix;
|
|
byte[] pfi;
|
|
List<AlcoholSession> sessions;
|
|
Dictionary<byte, uint> trackLengths;
|
|
List<AlcoholTrack> tracks;
|
|
|
|
internal Alcohol120(string outputPrefix)
|
|
{
|
|
this.outputPrefix = outputPrefix;
|
|
header = new AlcoholHeader
|
|
{
|
|
signature = "MEDIA DESCRIPTOR",
|
|
unknown1 = new ushort[] {0x0002, 0x0000},
|
|
unknown2 = new uint[2],
|
|
unknown3 = new uint[6],
|
|
unknown4 = new uint[3],
|
|
version = new byte[] {1, 5}
|
|
};
|
|
header.version[0] = 1;
|
|
header.version[1] = 5;
|
|
tracks = new List<AlcoholTrack>();
|
|
sessions = new List<AlcoholSession>();
|
|
trackLengths = new Dictionary<byte, uint>();
|
|
footer = new AlcoholFooter {widechar = 1};
|
|
}
|
|
|
|
internal void Close()
|
|
{
|
|
if(sessions.Count == 0 || tracks.Count == 0) return;
|
|
|
|
// Calculate first offsets
|
|
header.sessions = (ushort)sessions.Count;
|
|
header.sessionOffset = 88;
|
|
long nextOffset = 88 + sessions.Count * 24;
|
|
|
|
// Calculate session blocks
|
|
AlcoholSession[] sessionsArray = sessions.ToArray();
|
|
for(int i = 0; i < sessionsArray.Length; i++)
|
|
{
|
|
sessionsArray[i].allBlocks = (byte)(sessionsArray[i].lastTrack - sessionsArray[i].firstTrack + 1 +
|
|
sessionsArray[i].nonTrackBlocks);
|
|
sessionsArray[i].trackOffset = (uint)nextOffset;
|
|
nextOffset += sessionsArray[i].allBlocks * 80;
|
|
}
|
|
|
|
// Calculate track blocks
|
|
AlcoholTrack[] tracksArray = tracks.ToArray();
|
|
AlcoholTrackExtra[] extrasArray = new AlcoholTrackExtra[trackLengths.Count];
|
|
for(int i = 0; i < tracksArray.Length; i++)
|
|
{
|
|
if(tracksArray[i].point >= 0xA0) continue;
|
|
if(!trackLengths.TryGetValue(tracksArray[i].point, out uint trkLen)) continue;
|
|
|
|
if(tracksArray[i].mode == AlcoholTrackMode.Dvd) tracksArray[i].extraOffset = trkLen;
|
|
else
|
|
{
|
|
AlcoholTrackExtra extra = new AlcoholTrackExtra();
|
|
if(tracksArray[i].point == 1)
|
|
{
|
|
extra.pregap = 150;
|
|
sessionsArray[0].sessionStart = -150;
|
|
}
|
|
|
|
extra.sectors = trkLen;
|
|
extrasArray[tracksArray[i].point - 1] = extra;
|
|
tracksArray[i].extraOffset = (uint)nextOffset;
|
|
nextOffset += 8;
|
|
}
|
|
}
|
|
|
|
// DVD things
|
|
if(bca != null && bca.Length > 0)
|
|
{
|
|
header.bcaOffset = (uint)nextOffset;
|
|
header.bcaLength = (ushort)bca.Length;
|
|
nextOffset += bca.Length;
|
|
}
|
|
if(pfi != null && pfi.Length > 0 && dmi != null && dmi.Length > 0)
|
|
{
|
|
header.structuresOffset = (uint)nextOffset;
|
|
nextOffset += 4100;
|
|
}
|
|
|
|
for(int i = 0; i < tracksArray.Length; i++) tracksArray[i].footerOffset = (uint)nextOffset;
|
|
|
|
footer.filenameOffset = (uint)(nextOffset + 16);
|
|
|
|
byte[] filename = Encoding.Unicode.GetBytes(outputPrefix + extension);
|
|
|
|
// Open descriptor file here
|
|
FileStream descriptorFile =
|
|
new FileStream(outputPrefix + ".mds", FileMode.Create, FileAccess.ReadWrite, FileShare.None);
|
|
|
|
byte[] tmp = new byte[88];
|
|
IntPtr hdrPtr = Marshal.AllocHGlobal(88);
|
|
Marshal.StructureToPtr(header, hdrPtr, false);
|
|
Marshal.Copy(hdrPtr, tmp, 0, 88);
|
|
|
|
descriptorFile.Write(tmp, 0, tmp.Length);
|
|
|
|
foreach(AlcoholSession session in sessionsArray)
|
|
{
|
|
tmp = new byte[24];
|
|
IntPtr sesPtr = Marshal.AllocHGlobal(24);
|
|
Marshal.StructureToPtr(session, sesPtr, false);
|
|
Marshal.Copy(sesPtr, tmp, 0, 24);
|
|
descriptorFile.Write(tmp, 0, tmp.Length);
|
|
Marshal.FreeHGlobal(sesPtr);
|
|
}
|
|
|
|
foreach(AlcoholTrack track in tracksArray)
|
|
{
|
|
tmp = new byte[80];
|
|
IntPtr trkPtr = Marshal.AllocHGlobal(80);
|
|
Marshal.StructureToPtr(track, trkPtr, false);
|
|
Marshal.Copy(trkPtr, tmp, 0, 80);
|
|
descriptorFile.Write(tmp, 0, tmp.Length);
|
|
Marshal.FreeHGlobal(trkPtr);
|
|
}
|
|
|
|
if(header.type == AlcoholMediumType.Cd || header.type == AlcoholMediumType.Cdr ||
|
|
header.type == AlcoholMediumType.Cdrw)
|
|
foreach(AlcoholTrackExtra extra in extrasArray)
|
|
{
|
|
tmp = new byte[8];
|
|
IntPtr trkxPtr = Marshal.AllocHGlobal(8);
|
|
Marshal.StructureToPtr(extra, trkxPtr, false);
|
|
Marshal.Copy(trkxPtr, tmp, 0, 8);
|
|
descriptorFile.Write(tmp, 0, tmp.Length);
|
|
Marshal.FreeHGlobal(trkxPtr);
|
|
}
|
|
|
|
if(bca != null && bca.Length > 0)
|
|
{
|
|
header.bcaOffset = (uint)descriptorFile.Position;
|
|
header.bcaLength = (ushort)bca.Length;
|
|
descriptorFile.Write(bca, 0, bca.Length);
|
|
}
|
|
|
|
if(pfi != null && pfi.Length > 0 && dmi != null && dmi.Length > 0)
|
|
{
|
|
descriptorFile.Write(new byte[4], 0, 4);
|
|
descriptorFile.Write(dmi, 0, 2048);
|
|
descriptorFile.Write(pfi, 0, 2048);
|
|
}
|
|
|
|
tmp = new byte[16];
|
|
|
|
IntPtr ftrPtr = Marshal.AllocHGlobal(16);
|
|
Marshal.StructureToPtr(footer, ftrPtr, false);
|
|
Marshal.Copy(ftrPtr, tmp, 0, 16);
|
|
Marshal.FreeHGlobal(ftrPtr);
|
|
descriptorFile.Write(tmp, 0, tmp.Length);
|
|
|
|
descriptorFile.Write(filename, 0, filename.Length);
|
|
|
|
// This is because of marshalling strings
|
|
descriptorFile.Position = 15;
|
|
descriptorFile.WriteByte(0x52);
|
|
|
|
descriptorFile.Dispose();
|
|
}
|
|
|
|
internal void SetMediaType(MediaType type)
|
|
{
|
|
switch(type)
|
|
{
|
|
case MediaType.DVDDownload:
|
|
case MediaType.DVDPR:
|
|
case MediaType.DVDPRDL:
|
|
case MediaType.DVDPRW:
|
|
case MediaType.DVDPRWDL:
|
|
case MediaType.DVDR:
|
|
case MediaType.DVDRAM:
|
|
case MediaType.DVDRDL:
|
|
case MediaType.DVDRW:
|
|
case MediaType.DVDRWDL:
|
|
header.type = AlcoholMediumType.Dvdr;
|
|
break;
|
|
case MediaType.CD:
|
|
case MediaType.CDDA:
|
|
case MediaType.CDEG:
|
|
case MediaType.CDG:
|
|
case MediaType.CDI:
|
|
case MediaType.CDMIDI:
|
|
case MediaType.CDPLUS:
|
|
case MediaType.CDROM:
|
|
case MediaType.CDROMXA:
|
|
case MediaType.CDV:
|
|
case MediaType.DDCD:
|
|
case MediaType.DTSCD:
|
|
case MediaType.JaguarCD:
|
|
case MediaType.MEGACD:
|
|
case MediaType.PCD:
|
|
case MediaType.PS1CD:
|
|
case MediaType.PS2CD:
|
|
case MediaType.SATURNCD:
|
|
case MediaType.SuperCDROM2:
|
|
case MediaType.SVCD:
|
|
case MediaType.VCD:
|
|
case MediaType.VCDHD:
|
|
case MediaType.GDROM:
|
|
case MediaType.ThreeDO:
|
|
header.type = AlcoholMediumType.Cd;
|
|
break;
|
|
case MediaType.CDR:
|
|
case MediaType.DDCDR:
|
|
case MediaType.GDR:
|
|
header.type = AlcoholMediumType.Cdr;
|
|
break;
|
|
case MediaType.CDRW:
|
|
case MediaType.DDCDRW:
|
|
case MediaType.CDMO:
|
|
case MediaType.CDMRW:
|
|
header.type = AlcoholMediumType.Cdrw;
|
|
break;
|
|
default:
|
|
header.type = AlcoholMediumType.Dvd;
|
|
break;
|
|
}
|
|
}
|
|
|
|
internal void AddSessions(Session[] cdSessions)
|
|
{
|
|
foreach(AlcoholSession session in cdSessions.Select(cdSession => new AlcoholSession
|
|
{
|
|
firstTrack = (ushort)cdSession.StartTrack,
|
|
lastTrack = (ushort)cdSession.EndTrack,
|
|
sessionSequence = cdSession.SessionSequence
|
|
})) sessions.Add(session);
|
|
}
|
|
|
|
internal void SetTrackTypes(byte point, TrackType mode, TrackSubchannelType subMode)
|
|
{
|
|
AlcoholTrack[] trkArray = tracks.ToArray();
|
|
|
|
for(int i = 0; i < trkArray.Length; i++)
|
|
{
|
|
if(trkArray[i].point != point) continue;
|
|
|
|
switch(mode)
|
|
{
|
|
case TrackType.Audio:
|
|
trkArray[i].mode = AlcoholTrackMode.Audio;
|
|
break;
|
|
case TrackType.Data:
|
|
trkArray[i].mode = AlcoholTrackMode.Dvd;
|
|
break;
|
|
case TrackType.CdMode1:
|
|
trkArray[i].mode = AlcoholTrackMode.Mode1;
|
|
break;
|
|
case TrackType.CdMode2Formless:
|
|
trkArray[i].mode = AlcoholTrackMode.Mode2;
|
|
break;
|
|
case TrackType.CdMode2Form1:
|
|
trkArray[i].mode = AlcoholTrackMode.Mode2F1;
|
|
break;
|
|
case TrackType.CdMode2Form2:
|
|
trkArray[i].mode = AlcoholTrackMode.Mode2F2;
|
|
break;
|
|
default: throw new ArgumentOutOfRangeException(nameof(mode), mode, null);
|
|
}
|
|
|
|
switch(subMode)
|
|
{
|
|
case TrackSubchannelType.None:
|
|
trkArray[i].subMode = AlcoholSubchannelMode.None;
|
|
break;
|
|
case TrackSubchannelType.RawInterleaved:
|
|
trkArray[i].subMode = AlcoholSubchannelMode.Interleaved;
|
|
break;
|
|
case TrackSubchannelType.Packed:
|
|
case TrackSubchannelType.Raw:
|
|
case TrackSubchannelType.PackedInterleaved:
|
|
case TrackSubchannelType.Q16:
|
|
case TrackSubchannelType.Q16Interleaved:
|
|
throw new FeatureUnsupportedImageException("Specified subchannel type is not supported.");
|
|
default: throw new ArgumentOutOfRangeException(nameof(subMode), subMode, null);
|
|
}
|
|
|
|
tracks = new List<AlcoholTrack>(trkArray);
|
|
break;
|
|
}
|
|
}
|
|
|
|
internal void SetTrackSizes(byte point, int sectorSize, long startLba, long startOffset, long length)
|
|
{
|
|
AlcoholTrack[] trkArray = tracks.ToArray();
|
|
|
|
for(int i = 0; i < trkArray.Length; i++)
|
|
{
|
|
if(trkArray[i].point != point) continue;
|
|
|
|
trkArray[i].sectorSize = (ushort)sectorSize;
|
|
trkArray[i].startLba = (uint)startLba;
|
|
trkArray[i].startOffset = (ulong)startOffset;
|
|
|
|
tracks = new List<AlcoholTrack>(trkArray);
|
|
break;
|
|
}
|
|
|
|
if(trackLengths.ContainsKey(point)) trackLengths.Remove(point);
|
|
|
|
trackLengths.Add(point, (uint)length);
|
|
|
|
AlcoholSession[] sess = sessions.ToArray();
|
|
|
|
for(int i = 0; i < sess.Length; i++)
|
|
{
|
|
if(sess[i].firstTrack == point) sess[i].sessionStart = (int)startLba;
|
|
if(sess[i].lastTrack == point) sess[i].sessionEnd = (int)(startLba + length);
|
|
}
|
|
|
|
sessions = new List<AlcoholSession>(sess);
|
|
}
|
|
|
|
internal void AddTrack(byte adrCtl, byte tno, byte point, byte min, byte sec, byte frame, byte zero, byte pmin,
|
|
byte psec, byte pframe, byte session)
|
|
{
|
|
AlcoholTrack trk = new AlcoholTrack
|
|
{
|
|
mode = AlcoholTrackMode.NoData,
|
|
subMode = AlcoholSubchannelMode.None,
|
|
adrCtl = adrCtl,
|
|
tno = tno,
|
|
point = point,
|
|
min = min,
|
|
sec = sec,
|
|
frame = frame,
|
|
zero = zero,
|
|
pmin = pmin,
|
|
psec = psec,
|
|
pframe = pframe,
|
|
unknown = new byte[18],
|
|
files = 1,
|
|
unknown2 = new byte[24]
|
|
};
|
|
|
|
tracks.Add(trk);
|
|
|
|
if(point < 0xA0) return;
|
|
|
|
AlcoholSession[] sess = sessions.ToArray();
|
|
|
|
for(int i = 0; i < sess.Length; i++) if(sess[i].sessionSequence == session) sess[i].nonTrackBlocks++;
|
|
|
|
sessions = new List<AlcoholSession>(sess);
|
|
}
|
|
|
|
internal void AddBca(byte[] bca)
|
|
{
|
|
this.bca = bca;
|
|
}
|
|
|
|
internal void AddPfi(byte[] pfi)
|
|
{
|
|
if(pfi.Length == 2052)
|
|
{
|
|
this.pfi = new byte[2048];
|
|
Array.Copy(pfi, 4, this.pfi, 0, 2048);
|
|
}
|
|
else this.pfi = pfi;
|
|
}
|
|
|
|
internal void AddDmi(byte[] dmi)
|
|
{
|
|
if(dmi.Length == 2052)
|
|
{
|
|
this.dmi = new byte[2048];
|
|
Array.Copy(dmi, 4, this.dmi, 0, 2048);
|
|
}
|
|
else this.dmi = dmi;
|
|
}
|
|
|
|
internal void SetExtension(string extension)
|
|
{
|
|
this.extension = extension;
|
|
}
|
|
|
|
#region Internal Structures
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
struct AlcoholHeader
|
|
{
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] public string signature;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public byte[] version;
|
|
public AlcoholMediumType type;
|
|
public ushort sessions;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public ushort[] unknown1;
|
|
public ushort bcaLength;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public uint[] unknown2;
|
|
public uint bcaOffset;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public uint[] unknown3;
|
|
public uint structuresOffset;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public uint[] unknown4;
|
|
public uint sessionOffset;
|
|
public uint dpmOffset;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
struct AlcoholSession
|
|
{
|
|
public int sessionStart;
|
|
public int sessionEnd;
|
|
public ushort sessionSequence;
|
|
public byte allBlocks;
|
|
public byte nonTrackBlocks;
|
|
public ushort firstTrack;
|
|
public ushort lastTrack;
|
|
public uint unknown;
|
|
public uint trackOffset;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
struct AlcoholTrack
|
|
{
|
|
public AlcoholTrackMode mode;
|
|
public AlcoholSubchannelMode subMode;
|
|
public byte adrCtl;
|
|
public byte tno;
|
|
public byte point;
|
|
public byte min;
|
|
public byte sec;
|
|
public byte frame;
|
|
public byte zero;
|
|
public byte pmin;
|
|
public byte psec;
|
|
public byte pframe;
|
|
public uint extraOffset;
|
|
public ushort sectorSize;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 18)] public byte[] unknown;
|
|
public uint startLba;
|
|
public ulong startOffset;
|
|
public uint files;
|
|
public uint footerOffset;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] public byte[] unknown2;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
struct AlcoholTrackExtra
|
|
{
|
|
public uint pregap;
|
|
public uint sectors;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
struct AlcoholFooter
|
|
{
|
|
public uint filenameOffset;
|
|
public uint widechar;
|
|
public uint unknown1;
|
|
public uint unknown2;
|
|
}
|
|
#endregion Internal Structures
|
|
|
|
#region Internal enumerations
|
|
enum AlcoholMediumType : ushort
|
|
{
|
|
Cd = 0x00,
|
|
Cdr = 0x01,
|
|
Cdrw = 0x02,
|
|
Dvd = 0x10,
|
|
Dvdr = 0x12
|
|
}
|
|
|
|
enum AlcoholTrackMode : byte
|
|
{
|
|
NoData = 0x00,
|
|
Dvd = 0x02,
|
|
Audio = 0xA9,
|
|
Mode1 = 0xAA,
|
|
Mode2 = 0xAB,
|
|
Mode2F1 = 0xAC,
|
|
Mode2F2 = 0xAD
|
|
}
|
|
|
|
enum AlcoholSubchannelMode : byte
|
|
{
|
|
None = 0x00,
|
|
Interleaved = 0x08
|
|
}
|
|
#endregion Internal enumerations
|
|
}
|
|
} |