BizHawk/BizHawk.Emulation.DiscSystem/Internal/SectorSynth.cs

277 lines
7.6 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
namespace BizHawk.Emulation.DiscSystem
{
/// <summary>
/// Indicates which part of a sector are needing to be synthesized.
/// Sector synthesis may create too much data, but this is a hint as to what's needed
/// TODO - add a flag indicating whether clearing has happened
/// TODO - add output to the job indicating whether interleaving has happened. let the sector reader be responsible
/// </summary>
[Flags] enum ESectorSynthPart
{
/// <summary>
/// The data sector header is required. There's no header for audio tracks/sectors.
/// </summary>
Header16 = 1,
/// <summary>
/// The main 2048 user data bytes are required
/// </summary>
User2048 = 2,
/// <summary>
/// The 276 bytes of error correction are required
/// </summary>
ECC276 = 4,
/// <summary>
/// The 12 bytes preceding the ECC section are required (usually EDC and zero but also userdata sometimes)
/// </summary>
EDC12 = 8,
/// <summary>
/// The entire possible 276+12=288 bytes of ECM data is required (ECC276|EDC12)
/// </summary>
ECM288Complete = (ECC276 | EDC12),
/// <summary>
/// An alias for ECM288Complete
/// </summary>
ECMAny = ECM288Complete,
/// <summary>
/// A mode2 userdata section is required: the main 2048 user bytes AND the ECC and EDC areas
/// </summary>
User2336 = (User2048 | ECM288Complete),
/// <summary>
/// The complete sector userdata (2352 bytes) is required
/// </summary>
UserComplete = 15,
/// <summary>
/// An alias for UserComplete
/// </summary>
UserAny = UserComplete,
/// <summary>
/// An alias for UserComplete
/// </summary>
User2352 = UserComplete,
/// <summary>
/// SubP is required
/// </summary>
SubchannelP = 16,
/// <summary>
/// SubQ is required
/// </summary>
SubchannelQ = 32,
/// <summary>
/// Subchannels R-W (all except for P and Q)
/// </summary>
Subchannel_RSTUVW = (64|128|256|512|1024|2048),
/// <summary>
/// Complete subcode is required
/// </summary>
SubcodeComplete = (SubchannelP | SubchannelQ | Subchannel_RSTUVW),
/// <summary>
/// Any of the subcode might be required (just another way of writing SubcodeComplete)
/// </summary>
SubcodeAny = SubcodeComplete,
/// <summary>
/// The subcode should be deinterleaved
/// </summary>
SubcodeDeinterleave = 4096,
/// <summary>
/// The 100% complete sector is required including 2352 bytes of userdata and 96 bytes of subcode
/// </summary>
Complete2448 = SubcodeComplete | User2352,
}
/// <summary>
/// Basic unit of sector synthesis
/// </summary>
interface ISectorSynthJob2448
{
/// <summary>
/// Synthesizes a sctor with the given job parameters
/// </summary>
void Synth(SectorSynthJob job);
}
/// <summary>
/// Not a proper job? maybe with additional flags, it could be
/// </summary>
class SectorSynthJob
{
public int LBA;
public ESectorSynthPart Parts;
public byte[] DestBuffer2448;
public int DestOffset;
public SectorSynthParams Params;
public Disc Disc;
}
/// <summary>
/// an ISectorSynthProvider that just returns a value from an array of pre-made sectors
/// </summary>
class ArraySectorSynthProvider : ISectorSynthProvider
{
public List<ISectorSynthJob2448> Sectors = new List<ISectorSynthJob2448>();
public int FirstLBA;
public ISectorSynthJob2448 Get(int lba)
{
int index = lba - FirstLBA;
if (index < 0) return null;
if (index >= Sectors.Count) return null;
return Sectors[index];
}
}
/// <summary>
/// an ISectorSynthProvider that just returns a fixed synthesizer
/// </summary>
class SimpleSectorSynthProvider : ISectorSynthProvider
{
public ISectorSynthJob2448 SS;
public ISectorSynthJob2448 Get(int lba) { return SS; }
}
/// <summary>
/// Returns 'Patch' synth if the provided condition is met
/// </summary>
class ConditionalSectorSynthProvider : ISectorSynthProvider
{
Func<int,bool> Condition;
ISectorSynthJob2448 Patch;
ISectorSynthProvider Parent;
public void Install(Disc disc, Func<int, bool> condition, ISectorSynthJob2448 patch)
{
Parent = disc.SynthProvider;
disc.SynthProvider = this;
Condition = condition;
Patch = patch;
}
public ISectorSynthJob2448 Get(int lba)
{
if (Condition(lba))
return Patch;
else return Parent.Get(lba);
}
}
/// <summary>
/// When creating a disc, this is set with a callback that can deliver an ISectorSynthJob2448 for the given LBA
/// </summary>
interface ISectorSynthProvider
{
/// <summary>
/// Retrieves an ISectorSynthJob2448 for the given LBA
/// </summary>
ISectorSynthJob2448 Get(int lba);
}
/// <summary>
/// Generic parameters for sector synthesis.
/// To cut down on resource utilization, these can be stored in a disc and are tightly coupled to
/// the SectorSynths that have been setup for it
/// </summary>
struct SectorSynthParams
{
//public long[] BlobOffsets;
public MednaDisc MednaDisc;
}
class SS_PatchQ : ISectorSynthJob2448
{
public ISectorSynthJob2448 Original;
public byte[] Buffer_SubQ = new byte[12];
public void Synth(SectorSynthJob job)
{
Original.Synth(job);
if ((job.Parts & ESectorSynthPart.SubchannelQ) == 0)
return;
//apply patched subQ
for (int i = 0; i < 12; i++)
job.DestBuffer2448[2352 + 12 + i] = Buffer_SubQ[i];
}
}
class SS_Leadout : ISectorSynthJob2448
{
public int SessionNumber;
public DiscMountPolicy Policy;
public void Synth(SectorSynthJob job)
{
//be lazy, just generate the whole sector unconditionally
//this is mostly based on mednafen's approach, which was probably finely tailored for PSX
//heres the comments on the subject:
// I'm not trusting that the "control" field for the TOC leadout entry will always be set properly, so | the control fields for the last track entry
// and the leadout entry together before extracting the D2 bit. Audio track->data leadout is fairly benign though maybe noisy(especially if we ever implement
// data scrambling properly), but data track->audio leadout could break things in an insidious manner for the more accurate drive emulation code).
var ses = job.Disc.Structure.Sessions[SessionNumber];
int lba_relative = job.LBA - ses.LeadoutTrack.LBA;
//data is zero
int ts = lba_relative;
int ats = job.LBA;
const int ADR = 0x1; // Q channel data encodes position
EControlQ control = ses.LeadoutTrack.Control;
//ehhh? CDI?
//if(toc.tracks[toc.last_track].valid)
// control |= toc.tracks[toc.last_track].control & 0x4;
//else if(toc.disc_type == DISC_TYPE_CD_I)
// control |= 0x4;
control |= (EControlQ)(((int)ses.LastInformationTrack.Control) & 4);
SubchannelQ sq = new SubchannelQ();
sq.SetStatus(ADR, control);
sq.q_tno.BCDValue = 0xAA;
sq.q_index.BCDValue = 0x01;
sq.Timestamp = ts;
sq.AP_Timestamp = ats;
sq.zero = 0;
//finally, rely on a gap sector to do the heavy lifting to synthesize this
CUE.CueTrackType TrackType = CUE.CueTrackType.Audio;
if (ses.LeadoutTrack.IsData)
{
if (job.Disc.TOC.Session1Format == SessionFormat.Type20_CDXA || job.Disc.TOC.Session1Format == SessionFormat.Type10_CDI)
TrackType = CUE.CueTrackType.Mode2_2352;
else
TrackType = CUE.CueTrackType.Mode1_2352;
}
CUE.SS_Gap ss_gap = new CUE.SS_Gap()
{
Policy = Policy,
sq = sq,
TrackType = TrackType,
Pause = true //?
};
ss_gap.Synth(job);
}
}
}