Added parser for OpenTV's file delivery mechanism. Also began to work on an a parser for NDS'S file delivery mechanism.
This commit is contained in:
parent
887d983594
commit
8e57830409
117
skyscraper8/Experimentals/NdsSsu/NdsSsuDetector.cs
Normal file
117
skyscraper8/Experimentals/NdsSsu/NdsSsuDetector.cs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
using skyscraper5.Mpeg2;
|
||||||
|
using skyscraper5.Skyscraper.Plugins;
|
||||||
|
using skyscraper5.Skyscraper.Scraper;
|
||||||
|
using skyscraper5.Skyscraper.Scraper.StreamAutodetection;
|
||||||
|
using skyscraper8.Mpeg2.Psi;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace skyscraper8.Experimentals.NdsSsu
|
||||||
|
{
|
||||||
|
[SkyscraperPlugin]
|
||||||
|
internal class NdsSsuDetector : Contestant, NdsSsuHandler
|
||||||
|
{
|
||||||
|
public NdsSsuDetector(int pid) : base("NDS France SSU", pid)
|
||||||
|
{
|
||||||
|
PacketProcessor = new PsiDecoder(pid, new NdsSsuDetectorWorker(this, pid));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
PacketProcessor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext)
|
||||||
|
{
|
||||||
|
skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid,new NdsSsuParser(skyscraperContext,pid)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Introduce(ProgramContext programContext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class NdsSsuDetectorWorker : PrivateSectionParser
|
||||||
|
{
|
||||||
|
private readonly NdsSsuHandler _handler;
|
||||||
|
private readonly int _pid;
|
||||||
|
private bool[] tidExtensions;
|
||||||
|
|
||||||
|
public NdsSsuDetectorWorker(NdsSsuHandler handler, int pid)
|
||||||
|
{
|
||||||
|
_handler = handler;
|
||||||
|
_pid = pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleTable(byte tableId, int sourcePid, byte sectionNumber, byte lastSectionNumber, ushort tableIdExtension,
|
||||||
|
byte[] payload)
|
||||||
|
{
|
||||||
|
if (tableId != 0x9e)
|
||||||
|
{
|
||||||
|
_handler.OnNdsSsuError(_pid,NdsSsuError.WrongTable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload[0] != 0x01)
|
||||||
|
{
|
||||||
|
_handler.OnNdsSsuError(_pid, NdsSsuError.InvalidMagic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload[1] != 0xff)
|
||||||
|
{
|
||||||
|
_handler.OnNdsSsuError(_pid, NdsSsuError.InvalidMagic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload[2] != 0x00)
|
||||||
|
{
|
||||||
|
_handler.OnNdsSsuError(_pid, NdsSsuError.InvalidMagic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload[3] > 0x10)
|
||||||
|
{
|
||||||
|
_handler.OnNdsSsuError(_pid,NdsSsuError.InvalidMagic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tidExtensions == null)
|
||||||
|
tidExtensions = new bool[ushort.MaxValue];
|
||||||
|
|
||||||
|
if (tidExtensions[tableIdExtension])
|
||||||
|
{
|
||||||
|
_handler.OnNdsSsuProgress(_pid,tableIdExtension, sectionNumber, lastSectionNumber, payload[3]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_handler.OnNdsFileAccouncement(_pid,tableIdExtension);
|
||||||
|
tidExtensions[tableIdExtension] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnNdsSsuError(NdsSsuError error)
|
||||||
|
{
|
||||||
|
Score--;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnNdsSsuError(int pid, NdsSsuError error)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnNdsSsuProgress(int pid, ushort tableIdExtension, byte sectionNumber, byte lastSectionNumber, byte b)
|
||||||
|
{
|
||||||
|
Score++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnNdsFileAccouncement(int pid, ushort tableIdExtension)
|
||||||
|
{
|
||||||
|
Score--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
skyscraper8/Experimentals/NdsSsu/NdsSsuHandler.cs
Normal file
21
skyscraper8/Experimentals/NdsSsu/NdsSsuHandler.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace skyscraper8.Experimentals.NdsSsu
|
||||||
|
{
|
||||||
|
interface NdsSsuHandler
|
||||||
|
{
|
||||||
|
void OnNdsSsuError(int pid, NdsSsuError error);
|
||||||
|
void OnNdsSsuProgress(int pid, ushort tableIdExtension, byte sectionNumber, byte lastSectionNumber, byte b);
|
||||||
|
void OnNdsFileAccouncement(int pid, ushort tableIdExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum NdsSsuError
|
||||||
|
{
|
||||||
|
WrongTable,
|
||||||
|
InvalidMagic
|
||||||
|
}
|
||||||
|
}
|
||||||
140
skyscraper8/Experimentals/NdsSsu/NdsSsuParser.cs
Normal file
140
skyscraper8/Experimentals/NdsSsu/NdsSsuParser.cs
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
using skyscraper8.Mpeg2.Psi;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection.Metadata;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace skyscraper8.Experimentals.NdsSsu
|
||||||
|
{
|
||||||
|
internal class NdsSsuParser : PrivateSectionParser
|
||||||
|
{
|
||||||
|
private readonly NdsSsuHandler _handler;
|
||||||
|
private readonly int _pid;
|
||||||
|
|
||||||
|
public NdsSsuParser(NdsSsuHandler handler, int pid)
|
||||||
|
{
|
||||||
|
_handler = handler;
|
||||||
|
_pid = pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleTable(byte tableId, int sourcePid, byte sectionNumber, byte lastSectionNumber, ushort tableIdExtension, byte[] payload)
|
||||||
|
{
|
||||||
|
if (tableId != 0x9e)
|
||||||
|
{
|
||||||
|
_handler.OnNdsSsuError(_pid, NdsSsuError.WrongTable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload[0] == 0x00 && payload[1] == 0xff && payload[2] == 0x00 && payload[3] == 0x00)
|
||||||
|
{
|
||||||
|
//TODO: payload name
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload[0] != 0x01)
|
||||||
|
{
|
||||||
|
_handler.OnNdsSsuError(_pid, NdsSsuError.InvalidMagic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload[1] != 0xff)
|
||||||
|
{
|
||||||
|
_handler.OnNdsSsuError(_pid, NdsSsuError.InvalidMagic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload[2] != 0x00)
|
||||||
|
{
|
||||||
|
_handler.OnNdsSsuError(_pid, NdsSsuError.InvalidMagic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload[3] > 0x10)
|
||||||
|
{
|
||||||
|
_handler.OnNdsSsuError(_pid, NdsSsuError.InvalidMagic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataMaps == null)
|
||||||
|
dataMaps = new DataMap[ushort.MaxValue];
|
||||||
|
if (dataMaps[tableIdExtension] == null)
|
||||||
|
{
|
||||||
|
dataMaps[tableIdExtension] = new DataMap();
|
||||||
|
_handler.OnNdsFileAccouncement(_pid, tableIdExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataMaps[tableIdExtension].WasAlreadyPushed(payload, sectionNumber, lastSectionNumber))
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("the file is completed!");
|
||||||
|
}
|
||||||
|
dataMaps[tableIdExtension].PushPacket(payload,sectionNumber,lastSectionNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataMap[] dataMaps;
|
||||||
|
class DataMap
|
||||||
|
{
|
||||||
|
|
||||||
|
private Superblock[] superblocks;
|
||||||
|
public void PushPacket(byte[] payload,byte currentSection, byte lastSection)
|
||||||
|
{
|
||||||
|
byte maxNumSuperblocks = payload[3];
|
||||||
|
maxNumSuperblocks++;
|
||||||
|
if (superblocks == null)
|
||||||
|
superblocks = new Superblock[maxNumSuperblocks];
|
||||||
|
if (superblocks.Length < maxNumSuperblocks)
|
||||||
|
Array.Resize(ref superblocks, maxNumSuperblocks);
|
||||||
|
if (superblocks[payload[3]] == null)
|
||||||
|
{
|
||||||
|
superblocks[payload[3]] = new Superblock(lastSection);
|
||||||
|
}
|
||||||
|
superblocks[payload[3]].PushPacket(payload,currentSection,lastSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Superblock
|
||||||
|
{
|
||||||
|
private byte[][] blocks;
|
||||||
|
public Superblock(byte lastSection)
|
||||||
|
{
|
||||||
|
int maxNumBlocks = lastSection;
|
||||||
|
maxNumBlocks++;
|
||||||
|
blocks = new byte[maxNumBlocks][];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushPacket(byte[] payload, byte currentSection, byte lastSection)
|
||||||
|
{
|
||||||
|
int maxNumBlocks = lastSection;
|
||||||
|
maxNumBlocks++;
|
||||||
|
if (blocks.Length > maxNumBlocks)
|
||||||
|
Array.Resize(ref blocks, maxNumBlocks);
|
||||||
|
blocks[currentSection] = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WasAlreadyPushed(byte currentSection)
|
||||||
|
{
|
||||||
|
byte maxNumBlocks = currentSection;
|
||||||
|
maxNumBlocks++;
|
||||||
|
|
||||||
|
if (blocks.Length < currentSection)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return blocks[currentSection] != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WasAlreadyPushed(byte[] payload, byte sectionNumber, byte lastSectionNumber)
|
||||||
|
{
|
||||||
|
if (superblocks == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
byte maxNumSuperblocks = payload[3];
|
||||||
|
maxNumSuperblocks++;
|
||||||
|
if (superblocks.Length < maxNumSuperblocks)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return superblocks[payload[3]].WasAlreadyPushed(sectionNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
skyscraper8/Experimentals/OtvSsu/OtvSsuDetector.cs
Normal file
144
skyscraper8/Experimentals/OtvSsu/OtvSsuDetector.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using skyscraper5.Mpeg2;
|
||||||
|
using skyscraper5.Skyscraper.IO;
|
||||||
|
using skyscraper5.Skyscraper.Plugins;
|
||||||
|
using skyscraper5.Skyscraper.Scraper;
|
||||||
|
using skyscraper5.Skyscraper.Scraper.StreamAutodetection;
|
||||||
|
using skyscraper8.Mpeg2.Psi;
|
||||||
|
|
||||||
|
namespace skyscraper8.Experimentals.OtvSsu
|
||||||
|
{
|
||||||
|
[SkyscraperPlugin]
|
||||||
|
internal class OtvSsuDetector : Contestant, OtvSsuHandler
|
||||||
|
{
|
||||||
|
private OtvSsuDetectorParser parser;
|
||||||
|
public OtvSsuDetector(int pid) : base("OpenTV SSU", pid)
|
||||||
|
{
|
||||||
|
parser = new OtvSsuDetectorParser(this, pid);
|
||||||
|
PacketProcessor = new PsiDecoder(pid, parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
parser = null;
|
||||||
|
PacketProcessor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DeclareWinner(SkyscraperContext skyscraperContext, int pid, ProgramContext programContext)
|
||||||
|
{
|
||||||
|
skyscraperContext.DvbContext.RegisterPacketProcessor(pid, new PsiDecoder(pid,new OtvSsuParser(skyscraperContext)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Introduce(ProgramContext programContext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class OtvSsuDetectorParser : PrivateSectionParser
|
||||||
|
{
|
||||||
|
private readonly OtvSsuHandler _handler;
|
||||||
|
private readonly int _pid;
|
||||||
|
private List<Coordinate> _coordinates;
|
||||||
|
|
||||||
|
public OtvSsuDetectorParser(OtvSsuHandler handler, int pid)
|
||||||
|
{
|
||||||
|
_handler = handler;
|
||||||
|
_pid = pid;
|
||||||
|
_coordinates = new List<Coordinate>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coordinate
|
||||||
|
{
|
||||||
|
public ushort tableIdExtension;
|
||||||
|
public uint fileId;
|
||||||
|
public uint unknown1;
|
||||||
|
public uint length;
|
||||||
|
|
||||||
|
public Coordinate(ushort tableIdExtension1, uint u, uint unknown2, uint length1)
|
||||||
|
{
|
||||||
|
this.tableIdExtension = tableIdExtension1;
|
||||||
|
this.fileId = u;
|
||||||
|
this.unknown1 = unknown2;
|
||||||
|
this.length = length1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleTable(byte tableId, int sourcePid, byte sectionNumber, byte lastSectionNumber, ushort tableIdExtension,
|
||||||
|
byte[] payload)
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream(payload, false);
|
||||||
|
uint fileId = ms.ReadUInt32BE();
|
||||||
|
uint unknown1 = ms.ReadUInt32BE();
|
||||||
|
uint offset = ms.ReadUInt32BE();
|
||||||
|
uint length = ms.ReadUInt32BE();
|
||||||
|
if (length > Int32.MaxValue)
|
||||||
|
{
|
||||||
|
_handler.OnOtvSsuError(_pid, OtvSsuError.FileTooLarge);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (offset > length)
|
||||||
|
{
|
||||||
|
_handler.OnOtvSsuError(_pid,OtvSsuError.OffsetOutOfRange);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableIdExtension == 1 && (fileId & 0x00ff0000) == 0x00ff0000)
|
||||||
|
{
|
||||||
|
_handler.OnOtvSsuError(_pid, OtvSsuError.NdsOverlap);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Coordinate coordinate = _coordinates.Find(x =>
|
||||||
|
x.tableIdExtension == tableIdExtension && x.fileId == fileId && x.unknown1 == unknown1 &&
|
||||||
|
x.length == length);
|
||||||
|
if (coordinate == null)
|
||||||
|
{
|
||||||
|
coordinate = new Coordinate(tableIdExtension, fileId, unknown1, length);
|
||||||
|
_handler.OnOtvSsuFileAnnouncement(_pid,tableIdExtension, fileId, unknown1, length);
|
||||||
|
_coordinates.Add(coordinate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
coordinate.hits++;
|
||||||
|
_handler.OnOtvSsuBlock(_pid, tableIdExtension, fileId, unknown1, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleTable(byte tableId, int sourcePid, byte[] payload)
|
||||||
|
{
|
||||||
|
_handler.OnOtvSsuError(_pid, OtvSsuError.SectionSyntaxIndicatorMissing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnOtvSsuError(int pid, OtvSsuError offsetOutOfRange)
|
||||||
|
{
|
||||||
|
Score--;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnOtvSsuFileAnnouncement(int pid, ushort tableIdExtension, uint fileId, uint unknown1, uint length)
|
||||||
|
{
|
||||||
|
Score--;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnOtvSsuBlock(int pid, ushort tableIdExtension, uint fileId, uint unknown1, uint length)
|
||||||
|
{
|
||||||
|
Score++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnOtvSsuComplete(int sourcePid, Stream getStream, ushort tableIdExtension, uint fileId, uint unknown1,
|
||||||
|
uint length)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnOtvCheckFileAlreadyKnown(int sourcePid, ushort tableIdExtension, uint fileId, uint unknown1, uint length)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
skyscraper8/Experimentals/OtvSsu/OtvSsuHandler.cs
Normal file
26
skyscraper8/Experimentals/OtvSsu/OtvSsuHandler.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace skyscraper8.Experimentals.OtvSsu
|
||||||
|
{
|
||||||
|
internal interface OtvSsuHandler
|
||||||
|
{
|
||||||
|
void OnOtvSsuError(int pid, OtvSsuError offsetOutOfRange);
|
||||||
|
void OnOtvSsuFileAnnouncement(int pid, ushort tableIdExtension, uint fileId, uint unknown1, uint length);
|
||||||
|
void OnOtvSsuBlock(int pid, ushort tableIdExtension, uint fileId, uint unknown1, uint length);
|
||||||
|
void OnOtvSsuComplete(int sourcePid, Stream getStream, ushort tableIdExtension, uint fileId, uint unknown1, uint length);
|
||||||
|
bool OnOtvCheckFileAlreadyKnown(int sourcePid, ushort tableIdExtension, uint fileId, uint unknown1, uint length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum OtvSsuError
|
||||||
|
{
|
||||||
|
OffsetOutOfRange,
|
||||||
|
PacketDeliveredTooMuchData,
|
||||||
|
SectionSyntaxIndicatorMissing,
|
||||||
|
NdsOverlap,
|
||||||
|
FileTooLarge
|
||||||
|
}
|
||||||
|
}
|
||||||
202
skyscraper8/Experimentals/OtvSsu/OtvSsuParser.cs
Normal file
202
skyscraper8/Experimentals/OtvSsu/OtvSsuParser.cs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using skyscraper5.Skyscraper.IO;
|
||||||
|
using skyscraper8.Mpeg2.Psi;
|
||||||
|
|
||||||
|
namespace skyscraper8.Experimentals.OtvSsu
|
||||||
|
{
|
||||||
|
internal class OtvSsuParser : PrivateSectionParser
|
||||||
|
{
|
||||||
|
private readonly OtvSsuHandler _handler;
|
||||||
|
|
||||||
|
public OtvSsuParser(OtvSsuHandler handler)
|
||||||
|
{
|
||||||
|
_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<Coordinate, DataMap> _dataMaps;
|
||||||
|
protected override void HandleTable(byte tableId, int sourcePid, byte sectionNumber, byte lastSectionNumber, ushort tableIdExtension, byte[] payload)
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream(payload, false);
|
||||||
|
uint fileId = ms.ReadUInt32BE();
|
||||||
|
uint unknown1 = ms.ReadUInt32BE();
|
||||||
|
uint offset = ms.ReadUInt32BE();
|
||||||
|
uint length = ms.ReadUInt32BE();
|
||||||
|
if (offset > length)
|
||||||
|
{
|
||||||
|
_handler.OnOtvSsuError(sourcePid, OtvSsuError.OffsetOutOfRange);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length > Int32.MaxValue)
|
||||||
|
{
|
||||||
|
_handler.OnOtvSsuError(sourcePid, OtvSsuError.FileTooLarge);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tableIdExtension < 10 && (fileId & 0x00ff0000) == 0x00ff0000)
|
||||||
|
{
|
||||||
|
_handler.OnOtvSsuError(sourcePid, OtvSsuError.NdsOverlap);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Coordinate coords = new Coordinate(tableIdExtension, fileId, unknown1, length);
|
||||||
|
if (_dataMaps == null)
|
||||||
|
_dataMaps = new Dictionary<Coordinate, DataMap>();
|
||||||
|
DataMap map;
|
||||||
|
if (_dataMaps.ContainsKey(coords))
|
||||||
|
{
|
||||||
|
map = _dataMaps[coords];
|
||||||
|
_handler.OnOtvSsuBlock(sourcePid, tableIdExtension, fileId, unknown1, length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
map = new DataMap(length);
|
||||||
|
if (_handler.OnOtvCheckFileAlreadyKnown(sourcePid, tableIdExtension, fileId, unknown1, length))
|
||||||
|
{
|
||||||
|
map.Dispose();
|
||||||
|
_dataMaps.Add(coords, map);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_handler.OnOtvSsuFileAnnouncement(sourcePid, tableIdExtension, fileId, unknown1, length);
|
||||||
|
_dataMaps.Add(coords, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.IsComplete)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (map.WasWrittenBefore(offset))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
map.PushPacket(payload,offset);
|
||||||
|
if (map.IsErronous)
|
||||||
|
{
|
||||||
|
_handler.OnOtvSsuError(sourcePid, OtvSsuError.PacketDeliveredTooMuchData);
|
||||||
|
_dataMaps.Remove(coords);
|
||||||
|
map.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (map.IsComplete)
|
||||||
|
{
|
||||||
|
_handler.OnOtvSsuComplete(sourcePid, map.GetStream(), tableIdExtension, fileId, unknown1, length);
|
||||||
|
map.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coordinate
|
||||||
|
{
|
||||||
|
private readonly ushort _tableIdExtension;
|
||||||
|
private readonly uint _fileId;
|
||||||
|
private readonly uint _unknown1;
|
||||||
|
private readonly uint _length;
|
||||||
|
|
||||||
|
public Coordinate(ushort tableIdExtension, uint fileId, uint unknown1, uint length)
|
||||||
|
{
|
||||||
|
_tableIdExtension = tableIdExtension;
|
||||||
|
_fileId = fileId;
|
||||||
|
_unknown1 = unknown1;
|
||||||
|
_length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool Equals(Coordinate other)
|
||||||
|
{
|
||||||
|
return _tableIdExtension == other._tableIdExtension && _fileId == other._fileId && _unknown1 == other._unknown1 && _length == other._length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, obj))
|
||||||
|
return false;
|
||||||
|
if (ReferenceEquals(this, obj))
|
||||||
|
return true;
|
||||||
|
if (obj.GetType() != this.GetType())
|
||||||
|
return false;
|
||||||
|
return Equals((Coordinate)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(_tableIdExtension, _fileId, _unknown1, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DataMap : IDisposable
|
||||||
|
{
|
||||||
|
private MemoryStream ms;
|
||||||
|
private uint length;
|
||||||
|
private uint needed;
|
||||||
|
private List<uint> knownOffsets;
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
|
public DataMap(uint length)
|
||||||
|
{
|
||||||
|
ms = new MemoryStream((int)length);
|
||||||
|
this.length = length;
|
||||||
|
this.needed = length;
|
||||||
|
this.knownOffsets = new List<uint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushPacket(byte[] payload, uint offset)
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(this.ToString());
|
||||||
|
ms.Position = offset;
|
||||||
|
ms.Write(payload, 16, payload.Length - 16);
|
||||||
|
needed -= (uint)(payload.Length - 16);
|
||||||
|
knownOffsets.Add(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WasWrittenBefore(uint offset)
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
return true;
|
||||||
|
if (knownOffsets == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return knownOffsets.Contains(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsComplete
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
return false;
|
||||||
|
return needed == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsErronous
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(this.ToString());
|
||||||
|
return length < needed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream GetStream()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(this.ToString());
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
ms = null;
|
||||||
|
needed = 0;
|
||||||
|
knownOffsets = null;
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
skyscraper8/Mpeg2/Psi/PrivateSectionParser.cs
Normal file
59
skyscraper8/Mpeg2/Psi/PrivateSectionParser.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using skyscraper5.Mpeg2;
|
||||||
|
using skyscraper5.Skyscraper.IO;
|
||||||
|
|
||||||
|
namespace skyscraper8.Mpeg2.Psi
|
||||||
|
{
|
||||||
|
internal abstract class PrivateSectionParser : IPsiProcessor
|
||||||
|
{
|
||||||
|
public void GatherPsi(PsiSection section, int sourcePid)
|
||||||
|
{
|
||||||
|
byte[] bytes = section.GetData();
|
||||||
|
MemoryStream ms = new MemoryStream(bytes, false);
|
||||||
|
byte tableId = ms.ReadUInt8();
|
||||||
|
|
||||||
|
byte byteA = ms.ReadUInt8();
|
||||||
|
bool sectionSyntaxIndicator = (byteA & 0x80) != 0;
|
||||||
|
bool privateIndicator = (byteA & 0x40) != 0;
|
||||||
|
int reserved = (byteA & 0x30) >> 4;
|
||||||
|
int privateSectionLength = (byteA & 0xf);
|
||||||
|
privateSectionLength <<= 8;
|
||||||
|
privateSectionLength += ms.ReadUInt8();
|
||||||
|
if (!sectionSyntaxIndicator)
|
||||||
|
{
|
||||||
|
byte[] payload = ms.ReadBytes(ms.GetAvailableBytes());
|
||||||
|
HandleTable(tableId, sourcePid, payload);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ushort tableIdExtension = ms.ReadUInt16BE();
|
||||||
|
|
||||||
|
byte byteB = ms.ReadUInt8();
|
||||||
|
reserved = (byteB & 0xc0) >> 6;
|
||||||
|
int versionNumber = (byteB & 0x3e) >> 1;
|
||||||
|
bool currentNextIndicator = (byteB & 0x01) != 0;
|
||||||
|
|
||||||
|
byte sectionNumber = ms.ReadUInt8();
|
||||||
|
byte lastSectionNumber = ms.ReadUInt8();
|
||||||
|
int blockLength = privateSectionLength - 9;
|
||||||
|
byte[] payload = ms.ReadBytes(blockLength);
|
||||||
|
uint crc32 = ms.ReadUInt32BE();
|
||||||
|
HandleTable(tableId, sourcePid, sectionNumber, lastSectionNumber, tableIdExtension, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void HandleTable(byte tableId, int sourcePid, byte sectionNumber, byte lastSectionNumber, ushort tableIdExtension, byte[] payload)
|
||||||
|
{
|
||||||
|
throw new PsiException("You should implement the override for no section syntax indicator.");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void HandleTable(byte tableId, int sourcePid, byte[] payload)
|
||||||
|
{
|
||||||
|
throw new PsiException("You should implement the override for the section syntax indicator.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
skyscraper8/Mpeg2/Psi/PsiException.cs
Normal file
25
skyscraper8/Mpeg2/Psi/PsiException.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using skyscraper5.Mpeg2;
|
||||||
|
|
||||||
|
namespace skyscraper8.Mpeg2.Psi
|
||||||
|
{
|
||||||
|
class PsiException : Mpeg2Exception
|
||||||
|
{
|
||||||
|
public PsiException()
|
||||||
|
: base("PSI Error")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PsiException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PsiException(string message, Exception inner) : base(message, inner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -39,11 +39,11 @@ namespace skyscraper5
|
|||||||
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
|
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
|
||||||
private static void IntegrationTest()
|
private static void IntegrationTest()
|
||||||
{
|
{
|
||||||
List<SsdpDevice> ssdpDevices = SsdpClient.GetSsdpDevices(1000).ToList();
|
/*List<SsdpDevice> ssdpDevices = SsdpClient.GetSsdpDevices(1000).ToList();
|
||||||
foreach (SsdpDevice ssdpDevice in ssdpDevices)
|
foreach (SsdpDevice ssdpDevice in ssdpDevices)
|
||||||
{
|
{
|
||||||
Console.WriteLine("SSDP device: {0}", ssdpDevice.Server);
|
Console.WriteLine("SSDP device: {0}", ssdpDevice.Server);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
Console.WriteLine("yeet!");
|
Console.WriteLine("yeet!");
|
||||||
/*RtspClient rtspClient = new RtspClient("172.20.20.121", 554);
|
/*RtspClient rtspClient = new RtspClient("172.20.20.121", 554);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"profiles": {
|
"profiles": {
|
||||||
"skyscraper8": {
|
"skyscraper8": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"commandLineArgs": "\"C:\\devel\\skyscraper8\\skyscraper8\\bin\\Debug\\net8.0\\36E-12226L.ts\"",
|
"commandLineArgs": "\"C:\\Temp\\bem-service-a01.ts\"",
|
||||||
"remoteDebugEnabled": false
|
"remoteDebugEnabled": false
|
||||||
},
|
},
|
||||||
"Container (Dockerfile)": {
|
"Container (Dockerfile)": {
|
||||||
|
|||||||
@ -76,6 +76,8 @@ using System.Net;
|
|||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Security.Policy;
|
using System.Security.Policy;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using skyscraper8.Experimentals.NdsSsu;
|
||||||
|
using skyscraper8.Experimentals.OtvSsu;
|
||||||
using Tsubasa.IO;
|
using Tsubasa.IO;
|
||||||
using Platform = skyscraper5.Dvb.SystemSoftwareUpdate.Model.Platform;
|
using Platform = skyscraper5.Dvb.SystemSoftwareUpdate.Model.Platform;
|
||||||
using RntParser = skyscraper5.Dvb.TvAnytime.RntParser;
|
using RntParser = skyscraper5.Dvb.TvAnytime.RntParser;
|
||||||
@ -88,7 +90,7 @@ namespace skyscraper5.Skyscraper.Scraper
|
|||||||
UpdateNotificationEventHandler, DataCarouselEventHandler, RdsEventHandler, IScte35EventHandler,
|
UpdateNotificationEventHandler, DataCarouselEventHandler, RdsEventHandler, IScte35EventHandler,
|
||||||
IAutodetectionEventHandler, IRstEventHandler, IRntEventHandler, IMultiprotocolEncapsulationEventHandler, ObjectCarouselEventHandler, T2MIEventHandler,
|
IAutodetectionEventHandler, IRstEventHandler, IRntEventHandler, IMultiprotocolEncapsulationEventHandler, ObjectCarouselEventHandler, T2MIEventHandler,
|
||||||
IDisposable, IFrameGrabberEventHandler, IntEventHandler, IRctEventHandler, IGsEventHandler, ISkyscraperContext, IDocsisEventHandler, AbertisDecoderEventHandler, Id3Handler,
|
IDisposable, IFrameGrabberEventHandler, IntEventHandler, IRctEventHandler, IGsEventHandler, ISkyscraperContext, IDocsisEventHandler, AbertisDecoderEventHandler, Id3Handler,
|
||||||
InteractionChannelHandler, SgtEventHandler, IDvbNipEventHandler, UleEventHandler
|
InteractionChannelHandler, SgtEventHandler, IDvbNipEventHandler, UleEventHandler, OtvSsuHandler, NdsSsuHandler
|
||||||
{
|
{
|
||||||
public const bool ALLOW_STREAM_TYPE_AUTODETECTION = true;
|
public const bool ALLOW_STREAM_TYPE_AUTODETECTION = true;
|
||||||
public const bool ALLOW_FFMPEG_FRAMEGRABBER = true;
|
public const bool ALLOW_FFMPEG_FRAMEGRABBER = true;
|
||||||
@ -2910,5 +2912,46 @@ namespace skyscraper5.Skyscraper.Scraper
|
|||||||
}
|
}
|
||||||
throw new NotImplementedException("LLC/SNAP");
|
throw new NotImplementedException("LLC/SNAP");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnOtvSsuError(int pid, OtvSsuError offsetOutOfRange)
|
||||||
|
{
|
||||||
|
DvbContext.RegisterPacketProcessor(pid, new PacketDiscarder());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnOtvSsuFileAnnouncement(int pid, ushort tableIdExtension, uint fileId, uint unknown1, uint length)
|
||||||
|
{
|
||||||
|
LogEvent(SkyscraperContextEvent.OtvSsuFileDetected, String.Format("TID = {0}, FID = {1:X8}, Length = {2}", tableIdExtension, fileId, length));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnOtvSsuBlock(int pid, ushort tableIdExtension, uint fileId, uint unknown1, uint length)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnOtvSsuComplete(int sourcePid, Stream getStream, ushort tableIdExtension, uint fileId, uint unknown1,
|
||||||
|
uint length)
|
||||||
|
{
|
||||||
|
LogEvent(SkyscraperContextEvent.OtvSsuComplete, String.Format("TID = {0}, FID = {1:X8}, Length = {2}", tableIdExtension, fileId, length));
|
||||||
|
ObjectStorage.OnOtvSsuComplete(CurrentNetworkId, CurrentTransportStreamId, sourcePid, getStream, tableIdExtension, fileId, unknown1, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnOtvCheckFileAlreadyKnown(int sourcePid, ushort tableIdExtension, uint fileId, uint unknown1, uint length)
|
||||||
|
{
|
||||||
|
return ObjectStorage.OtvSsuTestFile(CurrentNetworkId, CurrentTransportStreamId, sourcePid, tableIdExtension, fileId, unknown1, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnNdsSsuError(int pid, NdsSsuError error)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnNdsSsuProgress(int pid, ushort tableIdExtension, byte sectionNumber, byte lastSectionNumber, byte b)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnNdsFileAccouncement(int pid, ushort tableIdExtension)
|
||||||
|
{
|
||||||
|
LogEvent(SkyscraperContextEvent.NdsSsuFileAnnounced, String.Format("PID = 0x{1:X4}, Table ID Extension = {0}", tableIdExtension, pid));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,6 +81,9 @@
|
|||||||
DvbNipService,
|
DvbNipService,
|
||||||
DvbNipMulticastSession,
|
DvbNipMulticastSession,
|
||||||
DvbNipMulticastGatewayConfigurationTransportSession,
|
DvbNipMulticastGatewayConfigurationTransportSession,
|
||||||
FluteFileAnnouncement
|
FluteFileAnnouncement,
|
||||||
|
OtvSsuFileDetected,
|
||||||
|
OtvSsuComplete,
|
||||||
|
NdsSsuFileAnnounced
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1524,6 +1524,34 @@ namespace skyscraper5.Skyscraper.Scraper.Storage.Filesystem
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool OtvSsuTestFile(int? currentNetworkId, int? currentTransportStreamId, int sourcePid, ushort tableIdExtension,
|
||||||
|
uint fileId, uint unknown1, uint length)
|
||||||
|
{
|
||||||
|
string cnid = currentNetworkId.HasValue ? currentNetworkId.Value.ToString() : "unknown";
|
||||||
|
string ctsid = currentTransportStreamId.HasValue ? currentTransportStreamId.Value.ToString() : "unknown";
|
||||||
|
string fname = String.Format("{0}_{1:X8}_{2:X8}.bin", tableIdExtension,fileId,unknown1);
|
||||||
|
string path = Path.Combine(rootDirectory.FullName, "OTV-SSU", cnid, ctsid, sourcePid.ToString(), fname);
|
||||||
|
FileInfo fi = new FileInfo(path);
|
||||||
|
return fi.Exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnOtvSsuComplete(int? currentNetworkId, int? currentTransportStreamId, int sourcePid, Stream getStream,
|
||||||
|
ushort tableIdExtension, uint fileId, uint unknown1, uint length)
|
||||||
|
{
|
||||||
|
string cnid = currentNetworkId.HasValue ? currentNetworkId.Value.ToString() : "unknown";
|
||||||
|
string ctsid = currentTransportStreamId.HasValue ? currentTransportStreamId.Value.ToString() : "unknown";
|
||||||
|
string fname = String.Format("{0}_{1:X8}_{2:X8}.bin", tableIdExtension, fileId, unknown1);
|
||||||
|
string path = Path.Combine(rootDirectory.FullName, "OTV-SSU", cnid, ctsid, sourcePid.ToString(), fname);
|
||||||
|
FileInfo fi = new FileInfo(path);
|
||||||
|
fi.Directory.EnsureExists();
|
||||||
|
FileStream fileStream = fi.OpenWrite();
|
||||||
|
getStream.Position = 0;
|
||||||
|
getStream.CopyTo(fileStream);
|
||||||
|
fileStream.Flush(true);
|
||||||
|
fileStream.Close();
|
||||||
|
getStream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
class NipPds
|
class NipPds
|
||||||
{
|
{
|
||||||
public DateTime VersionUpdate;
|
public DateTime VersionUpdate;
|
||||||
|
|||||||
@ -99,5 +99,17 @@ namespace skyscraper8.Skyscraper.Scraper.Storage
|
|||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool OtvSsuTestFile(int? currentNetworkId, int? currentTransportStreamId, int sourcePid, ushort tableIdExtension,
|
||||||
|
uint fileId, uint unknown1, uint length)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnOtvSsuComplete(int? currentNetworkId, int? currentTransportStreamId, int sourcePid, Stream getStream,
|
||||||
|
ushort tableIdExtension, uint fileId, uint unknown1, uint length)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,5 +31,7 @@ namespace skyscraper8.Skyscraper.Scraper.Storage
|
|||||||
void StoreRfSpectrum(Guid jobGuid, RfSpectrumData rfSpectrum);
|
void StoreRfSpectrum(Guid jobGuid, RfSpectrumData rfSpectrum);
|
||||||
void DeleteIqGraph(Guid selectedGuid, int frequencyItem1, SatelliteDeliverySystemDescriptor.PolarizationEnum frequencyItem2);
|
void DeleteIqGraph(Guid selectedGuid, int frequencyItem1, SatelliteDeliverySystemDescriptor.PolarizationEnum frequencyItem2);
|
||||||
void DeleteRfSpectrum(Guid selectedGuid);
|
void DeleteRfSpectrum(Guid selectedGuid);
|
||||||
|
bool OtvSsuTestFile(int? currentNetworkId, int? currentTransportStreamId, int sourcePid, ushort tableIdExtension, uint fileId, uint unknown1, uint length);
|
||||||
|
void OnOtvSsuComplete(int? currentNetworkId, int? currentTransportStreamId, int sourcePid, Stream getStream, ushort tableIdExtension, uint fileId, uint unknown1, uint length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user