Cleanup
This commit is contained in:
+11
-3
@@ -1,5 +1,6 @@
|
|||||||
using LibIRD;
|
using LibIRD;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace BuildIRD
|
namespace BuildIRD
|
||||||
{
|
{
|
||||||
@@ -7,12 +8,19 @@ namespace BuildIRD
|
|||||||
{
|
{
|
||||||
static void Main()
|
static void Main()
|
||||||
{
|
{
|
||||||
byte[] discKey = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
Console.OutputEncoding = Encoding.UTF8;
|
||||||
|
|
||||||
|
// Create new reproducible redump-style IRD with a key
|
||||||
|
byte[] discKey = new byte[] { 0x1A, 0x2D, 0x88, 0xFC, 0x19, 0x37, 0x27, 0x44, 0x11, 0x5E, 0xE9, 0x83, 0xA0, 0x47, 0xE2, 0xD5 };
|
||||||
IRD ird1 = new ReIRD("./game.iso", discKey);
|
IRD ird1 = new ReIRD("./game.iso", discKey);
|
||||||
IRD ird2 = new ReIRD("./game.iso", "./log.getkey.log");
|
|
||||||
ird1.Write("./test1.ird");
|
ird1.Write("./test1.ird");
|
||||||
|
Console.WriteLine("IRD for " + ird1.Title + " created");
|
||||||
|
|
||||||
|
// Create new reproducible redump-style IRD with a GetKey log
|
||||||
|
string logPath = "./log.getkey.log";
|
||||||
|
IRD ird2 = new ReIRD("./game.iso", logPath);
|
||||||
ird2.Write("./test2.ird");
|
ird2.Write("./test2.ird");
|
||||||
Console.WriteLine(ird1.Title);
|
Console.WriteLine("IRD for " + ird2.Title + " created");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+95
-92
@@ -8,8 +8,9 @@ using System.Text;
|
|||||||
namespace LibIRD
|
namespace LibIRD
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ISO Rebuild Data: Generation and writing to IRD file
|
/// ISO Rebuild Data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>Generates IRD fields and writes to an IRD file</remarks>
|
||||||
public class IRD : PS3ISO
|
public class IRD : PS3ISO
|
||||||
{
|
{
|
||||||
#region Constants
|
#region Constants
|
||||||
@@ -18,7 +19,7 @@ namespace LibIRD
|
|||||||
/// IRD file signature
|
/// IRD file signature
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>"3IRD"</remarks>
|
/// <remarks>"3IRD"</remarks>
|
||||||
private static readonly byte[] Magic = new byte[] { 0x33, 0x49, 0x52, 0x44 };
|
private static readonly byte[] Magic = { 0x33, 0x49, 0x52, 0x44 };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AES CBC Encryption Key for Data 1 (Disc Key)
|
/// AES CBC Encryption Key for Data 1 (Disc Key)
|
||||||
@@ -66,14 +67,16 @@ namespace LibIRD
|
|||||||
public uint UID { get; set; } = 0x00000000; // Default to zeroed UID
|
public uint UID { get; set; } = 0x00000000; // Default to zeroed UID
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extra Config, usually 0x0000
|
/// Extra Config
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort ExtraConfig { get; set; } = 0x0000;
|
/// <remarks>Reserved, usually set to 0x0000</remarks>
|
||||||
|
public ushort ExtraConfig { get; set; } = 0x0000; // Default to zero
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attachments, usually 0x0000
|
/// Attachments
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Attachments { get; set; } = 0x0000;
|
/// <remarks>Reserved, usually set to 0x0000</remarks>
|
||||||
|
public ushort Attachments { get; set; } = 0x0000; // Default to zero
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// D1 key
|
/// D1 key
|
||||||
@@ -139,12 +142,12 @@ namespace LibIRD
|
|||||||
private protected void GenerateD1(byte[] key)
|
private protected void GenerateD1(byte[] key)
|
||||||
{
|
{
|
||||||
// Validate key
|
// Validate key
|
||||||
if (key == null || key.Length <= 0)
|
if (key == null)
|
||||||
throw new ArgumentNullException(nameof(key));
|
throw new ArgumentNullException(nameof(key));
|
||||||
if (key.Length != 16)
|
if (key.Length != 16)
|
||||||
throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(key));
|
throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(key));
|
||||||
|
|
||||||
// AES decryption
|
// Setup AES decryption
|
||||||
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
|
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
|
||||||
|
|
||||||
// Set AES settings
|
// Set AES settings
|
||||||
@@ -154,14 +157,14 @@ namespace LibIRD
|
|||||||
aes.Mode = CipherMode.CBC;
|
aes.Mode = CipherMode.CBC;
|
||||||
|
|
||||||
// Perform AES decryption
|
// Perform AES decryption
|
||||||
using ICryptoTransform decryptor = aes.CreateDecryptor();
|
using MemoryStream stream = new();
|
||||||
MemoryStream ms = new();
|
using ICryptoTransform dec = aes.CreateDecryptor();
|
||||||
CryptoStream cs = new(ms, decryptor, CryptoStreamMode.Write);
|
using CryptoStream cs = new(stream, dec, CryptoStreamMode.Write);
|
||||||
cs.Write(key, 0, 16);
|
cs.Write(key, 0, 16);
|
||||||
cs.FlushFinalBlock();
|
cs.FlushFinalBlock();
|
||||||
Data1Key = ms.ToArray();
|
|
||||||
ms.Close();
|
// Save decrypted key to field
|
||||||
cs.Close();
|
Data1Key = stream.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -173,12 +176,12 @@ namespace LibIRD
|
|||||||
private protected void GenerateD2(byte[] id)
|
private protected void GenerateD2(byte[] id)
|
||||||
{
|
{
|
||||||
// Validate id
|
// Validate id
|
||||||
if (id == null || id.Length <= 0)
|
if (id == null)
|
||||||
throw new ArgumentNullException(nameof(id));
|
throw new ArgumentNullException(nameof(id));
|
||||||
if (id.Length != 16)
|
if (id.Length != 16)
|
||||||
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(id));
|
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(id));
|
||||||
|
|
||||||
// AES encryption
|
// Setup AES encryption
|
||||||
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
|
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
|
||||||
|
|
||||||
// Set AES settings
|
// Set AES settings
|
||||||
@@ -188,14 +191,14 @@ namespace LibIRD
|
|||||||
aes.Mode = CipherMode.CBC;
|
aes.Mode = CipherMode.CBC;
|
||||||
|
|
||||||
// Perform AES encryption
|
// Perform AES encryption
|
||||||
using ICryptoTransform encryptor = aes.CreateEncryptor();
|
using MemoryStream stream = new();
|
||||||
MemoryStream ms = new();
|
using ICryptoTransform enc = aes.CreateEncryptor();
|
||||||
CryptoStream cs = new(ms, encryptor, CryptoStreamMode.Write);
|
using CryptoStream cs = new(stream, enc, CryptoStreamMode.Write);
|
||||||
cs.Write(id, 0, 16);
|
cs.Write(id, 0, 16);
|
||||||
cs.FlushFinalBlock();
|
cs.FlushFinalBlock();
|
||||||
Data2Key = ms.ToArray();
|
|
||||||
ms.Close();
|
// Save encrypted key to field
|
||||||
cs.Close();
|
Data2Key = stream.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -207,7 +210,7 @@ namespace LibIRD
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private protected IRD(string isoPath) : base(isoPath)
|
private protected IRD(string isoPath) : base(isoPath)
|
||||||
{
|
{
|
||||||
// Assumes that derived class will set private variables and generate in its own constructor
|
// Assumes that internally derived class will set D1/D2/PIC in its own constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -228,78 +231,78 @@ namespace LibIRD
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor that reads required fields from .getkey.log file
|
/// Constructor that reads required fields from .getkey.log file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="isoPath"></param>
|
/// <param name="isoPath">Path to the ISO</param>
|
||||||
/// <param name="getKeyLog"></param>
|
/// <param name="getKeyLog">Path to the .getkey.log file</param>
|
||||||
|
/// <exception cref="ArgumentNullException"></exception>
|
||||||
|
/// <exception cref="InvalidDataException"></exception>
|
||||||
public IRD(string isoPath, string getKeyLog) : base(isoPath)
|
public IRD(string isoPath, string getKeyLog) : base(isoPath)
|
||||||
{
|
{
|
||||||
// Validate .getkey.log file path
|
// Validate .getkey.log file path
|
||||||
if (getKeyLog == null || !File.Exists(getKeyLog))
|
if (getKeyLog == null)
|
||||||
throw new ArgumentNullException(nameof(getKeyLog));
|
throw new ArgumentNullException(nameof(getKeyLog));
|
||||||
|
if (!File.Exists(getKeyLog))
|
||||||
// Initialise fields
|
throw new FileNotFoundException(nameof(getKeyLog));
|
||||||
byte[] discKey;
|
|
||||||
byte[] discID;
|
|
||||||
byte[] discPIC;
|
|
||||||
|
|
||||||
// Read from .getkey.log file
|
// Read from .getkey.log file
|
||||||
using (var sr = File.OpenText(getKeyLog))
|
using StreamReader sr = File.OpenText(getKeyLog);
|
||||||
|
|
||||||
|
// Determine whether GetKey was successful
|
||||||
|
string line;
|
||||||
|
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("get_dec_key succeeded!") == false) ;
|
||||||
|
if (line == null)
|
||||||
|
throw new InvalidDataException(".getkey.log contains errors");
|
||||||
|
|
||||||
|
// Look for Disc Key in log
|
||||||
|
byte[] discKey;
|
||||||
|
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_key = ") == false) ;
|
||||||
|
if (line == null)
|
||||||
|
throw new InvalidDataException("Could not find Disc Key in .getkey.log");
|
||||||
|
// Get Disc Key from log
|
||||||
|
string discKeyStr = line["disc_key = ".Length..];
|
||||||
|
// Validate Disc Key from log
|
||||||
|
if (discKeyStr.Length != 32)
|
||||||
|
throw new InvalidDataException("Unexpected Disc Key in .getkey.log");
|
||||||
|
// Convert Disc Key to byte array
|
||||||
|
discKey = Utilities.HexToBytes(discKeyStr);
|
||||||
|
|
||||||
|
// Read Disc ID
|
||||||
|
byte[] discID;
|
||||||
|
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_id = ") == false) ;
|
||||||
|
if (line == null)
|
||||||
|
throw new InvalidDataException("Could not find Disc ID in .getkey.log");
|
||||||
|
// Get Disc ID from log
|
||||||
|
string discIDStr = line["disc_id = ".Length..];
|
||||||
|
// Validate Disc ID from log
|
||||||
|
if (discIDStr.Length != 32)
|
||||||
|
throw new InvalidDataException("Unexpected Disc ID in .getkey.log");
|
||||||
|
// Replace X's in Disc ID with 00000001
|
||||||
|
discIDStr = discIDStr[..24] + "00000001";
|
||||||
|
// Convert Disc ID to byte array
|
||||||
|
discID = Utilities.HexToBytes(discIDStr);
|
||||||
|
|
||||||
|
// Look for PIC in log
|
||||||
|
byte[] discPIC;
|
||||||
|
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("PIC:") == false) ;
|
||||||
|
if (line == null)
|
||||||
|
throw new InvalidDataException("Could not find PIC in .getkey.log");
|
||||||
|
// Get PIC from log
|
||||||
|
string discPICStr = "";
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
discPICStr += sr.ReadLine() ?? throw new InvalidDataException("Incomplete PIC in .getkey.log");
|
||||||
|
// Validate PIC from log
|
||||||
|
if (discPICStr.Length != 256)
|
||||||
|
throw new InvalidDataException("Unexpected PIC in .getkey.log");
|
||||||
|
// Convert PIC to byte array
|
||||||
|
discPIC = Utilities.HexToBytes(discPICStr[..230]);
|
||||||
|
|
||||||
|
// Double check for warnings in .getkey.log
|
||||||
|
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("WARNING") == false && line.Trim().StartsWith("SUCCESS") == false)
|
||||||
{
|
{
|
||||||
string line;
|
string t = line.Trim();
|
||||||
|
if (t.StartsWith("WARNING"))
|
||||||
// Determine whether GetKey was successful
|
|
||||||
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("get_dec_key succeeded!") == false) ;
|
|
||||||
if (line == null)
|
|
||||||
throw new InvalidDataException(".getkey.log contains errors");
|
throw new InvalidDataException(".getkey.log contains errors");
|
||||||
|
else if (t.StartsWith("SUCCESS"))
|
||||||
// Look for Disc Key in log
|
break;
|
||||||
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_key = ") == false) ;
|
|
||||||
if (line == null)
|
|
||||||
throw new InvalidDataException("Could not find Disc Key in .getkey.log");
|
|
||||||
// Get Disc Key from log
|
|
||||||
string discKeyStr = line["disc_key = ".Length..];
|
|
||||||
// Validate Disc Key from log
|
|
||||||
if (discKeyStr.Length != 32)
|
|
||||||
throw new InvalidDataException("Unexpected Disc Key in .getkey.log");
|
|
||||||
// Convert Disc Key to byte array
|
|
||||||
discKey = Utilities.HexToBytes(discKeyStr);
|
|
||||||
|
|
||||||
// Read Disc ID
|
|
||||||
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_id = ") == false) ;
|
|
||||||
if (line == null)
|
|
||||||
throw new InvalidDataException("Could not find Disc ID in .getkey.log");
|
|
||||||
// Get Disc ID from log
|
|
||||||
string discIDStr = line["disc_id = ".Length..];
|
|
||||||
// Validate Disc ID from log
|
|
||||||
if (discIDStr.Length != 32)
|
|
||||||
throw new InvalidDataException("Unexpected Disc ID in .getkey.log");
|
|
||||||
// Replace X's in Disc ID with 00000001
|
|
||||||
discIDStr = discIDStr[..24] + "00000001";
|
|
||||||
// Convert Disc ID to byte array
|
|
||||||
discID = Utilities.HexToBytes(discIDStr);
|
|
||||||
|
|
||||||
// Look for PIC in log
|
|
||||||
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("PIC:") == false) ;
|
|
||||||
if (line == null)
|
|
||||||
throw new InvalidDataException("Could not find PIC in .getkey.log");
|
|
||||||
// Get PIC from log
|
|
||||||
string discPICStr = "";
|
|
||||||
for (int i = 0; i < 8; i++)
|
|
||||||
discPICStr += sr.ReadLine() ?? throw new InvalidDataException("Incomplete PIC in .getkey.log");
|
|
||||||
// Validate PIC from log
|
|
||||||
if (discPICStr.Length != 256)
|
|
||||||
throw new InvalidDataException("Unexpected PIC in .getkey.log");
|
|
||||||
// Convert PIC to byte array
|
|
||||||
discPIC = Utilities.HexToBytes(discPICStr[..230]);
|
|
||||||
|
|
||||||
// Check for warnings in .getkey.log
|
|
||||||
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("WARNING") == false && line.Trim().StartsWith("SUCCESS") == false)
|
|
||||||
{
|
|
||||||
string t = line.Trim();
|
|
||||||
if (t.StartsWith("WARNING"))
|
|
||||||
throw new InvalidDataException(".getkey.log contains errors");
|
|
||||||
else if (t.StartsWith("SUCCESS"))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse DiscKey, DiscID, and PIC
|
// Parse DiscKey, DiscID, and PIC
|
||||||
@@ -315,15 +318,16 @@ namespace LibIRD
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write IRD data to file
|
/// Write IRD data to file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="irdPath">Path to IRD file to be written to</param>
|
/// <param name="irdPath">Path to the ISO</param>
|
||||||
|
/// <exception cref="ArgumentNullException"></exception>
|
||||||
public void Write(string irdPath)
|
public void Write(string irdPath)
|
||||||
{
|
{
|
||||||
// Validate irdPath
|
// Validate irdPath
|
||||||
if (irdPath == null || irdPath.Length <= 0)
|
if (irdPath == null || irdPath.Length <= 0)
|
||||||
throw new ArgumentNullException(nameof(irdPath));
|
throw new ArgumentNullException(nameof(irdPath));
|
||||||
|
|
||||||
// Create new stream to write to
|
// Create new stream to uncompressed IRD contents
|
||||||
MemoryStream stream = new();
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
// Write IRD data to stream in order
|
// Write IRD data to stream in order
|
||||||
using (BinaryWriter bw = new(stream, Encoding.UTF8, true))
|
using (BinaryWriter bw = new(stream, Encoding.UTF8, true))
|
||||||
@@ -410,7 +414,7 @@ namespace LibIRD
|
|||||||
bw.Write(UID);
|
bw.Write(UID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the little-endian 32-bit "IEEE 802.3" CRC value of the entire stream
|
// Calculate the little-endian 32-bit "IEEE 802.3" CRC value of the IRD contents so far
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
Crc32 crc32 = new();
|
Crc32 crc32 = new();
|
||||||
crc32.Append(stream);
|
crc32.Append(stream);
|
||||||
@@ -426,7 +430,6 @@ namespace LibIRD
|
|||||||
// Write entire gzipped IRD stream to file
|
// Write entire gzipped IRD stream to file
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
stream.CopyTo(gzStream);
|
stream.CopyTo(gzStream);
|
||||||
stream.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
+1
-1
@@ -195,7 +195,7 @@ namespace LibIRD
|
|||||||
RegionHashes = new byte[RegionCount][];
|
RegionHashes = new byte[RegionCount][];
|
||||||
for (int i = 0; i < RegionCount; i++)
|
for (int i = 0; i < RegionCount; i++)
|
||||||
RegionHashes[i] = NullMD5;
|
RegionHashes[i] = NullMD5;
|
||||||
FileCount = 1;
|
FileCount = 13;
|
||||||
FileKeys = new ulong[FileCount];
|
FileKeys = new ulong[FileCount];
|
||||||
for (int i = 0; i < FileCount; i++)
|
for (int i = 0; i < FileCount; i++)
|
||||||
FileKeys[i] = (ulong)(i * 16);
|
FileKeys[i] = (ulong)(i * 16);
|
||||||
|
|||||||
+20
-43
@@ -5,53 +5,30 @@ namespace LibIRD
|
|||||||
internal class Utilities
|
internal class Utilities
|
||||||
{
|
{
|
||||||
// Helper function to convert a hex string to a byte array
|
// Helper function to convert a hex string to a byte array
|
||||||
// Source: https://codereview.stackexchange.com/a/53846
|
// Original source: https://codereview.stackexchange.com/a/53846
|
||||||
|
|
||||||
private static int HexToInt(char c)
|
private static int HexToInt(char c)
|
||||||
{
|
{
|
||||||
switch (c)
|
return c switch
|
||||||
{
|
{
|
||||||
case '0':
|
'0' => 0,
|
||||||
return 0;
|
'1' => 1,
|
||||||
case '1':
|
'2' => 2,
|
||||||
return 1;
|
'3' => 3,
|
||||||
case '2':
|
'4' => 4,
|
||||||
return 2;
|
'5' => 5,
|
||||||
case '3':
|
'6' => 6,
|
||||||
return 3;
|
'7' => 7,
|
||||||
case '4':
|
'8' => 8,
|
||||||
return 4;
|
'9' => 9,
|
||||||
case '5':
|
'a' or 'A' => 10,
|
||||||
return 5;
|
'b' or 'B' => 11,
|
||||||
case '6':
|
'c' or 'C' => 12,
|
||||||
return 6;
|
'd' or 'D' => 13,
|
||||||
case '7':
|
'e' or 'E' => 14,
|
||||||
return 7;
|
'f' or 'F' => 15,
|
||||||
case '8':
|
_ => throw new FormatException("Unrecognized hex char " + c),
|
||||||
return 8;
|
};
|
||||||
case '9':
|
|
||||||
return 9;
|
|
||||||
case 'a':
|
|
||||||
case 'A':
|
|
||||||
return 10;
|
|
||||||
case 'b':
|
|
||||||
case 'B':
|
|
||||||
return 11;
|
|
||||||
case 'c':
|
|
||||||
case 'C':
|
|
||||||
return 12;
|
|
||||||
case 'd':
|
|
||||||
case 'D':
|
|
||||||
return 13;
|
|
||||||
case 'e':
|
|
||||||
case 'E':
|
|
||||||
return 14;
|
|
||||||
case 'f':
|
|
||||||
case 'F':
|
|
||||||
return 15;
|
|
||||||
default:
|
|
||||||
throw new FormatException("Unrecognized hex char " + c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly byte[,] ByteLookup = new byte[,]
|
private static readonly byte[,] ByteLookup = new byte[,]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace PrintParams
|
|||||||
|
|
||||||
if (File.Exists(filename))
|
if (File.Exists(filename))
|
||||||
{
|
{
|
||||||
ParamSFO paramSFO = new ParamSFO("./PARAM.SFO");
|
ParamSFO paramSFO = new("./PARAM.SFO");
|
||||||
Console.WriteLine(paramSFO["TITLE_ID"]);
|
Console.WriteLine(paramSFO["TITLE_ID"]);
|
||||||
paramSFO.Print();
|
paramSFO.Print();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user