Client <-> protocol questions
Moderator: Moderators
Client <-> protocol questions
I'm developing an dc client and I have some questions..
1) In the client to client protocol, why is a "$1" added to the filename in the $Get command? I get like this: "$Get MyList.DcLst$1|" ?
2) Is dc using any ack packets when sending files? I've done some sniffing and it seems like a data-packet always is followed by a packet from the client that receives the data.. Is this a feature of DC or just a part od the tcp/ip protocol?
3) What size should the data-packets be? DC++ seems to use 1460b, is this correct? Does it even matter which size you use?
4) The Huffman encoding =)
should I just use ordinary Huffman on an uncompressed DCList file?
---
uncompressed DCList =
dirname
<tab>dirname
<tab><filename>file|size
---
I've had a look at the DC src at it seems to write checksums in the beginning of the file?
thanks in advance!
1) In the client to client protocol, why is a "$1" added to the filename in the $Get command? I get like this: "$Get MyList.DcLst$1|" ?
2) Is dc using any ack packets when sending files? I've done some sniffing and it seems like a data-packet always is followed by a packet from the client that receives the data.. Is this a feature of DC or just a part od the tcp/ip protocol?
3) What size should the data-packets be? DC++ seems to use 1460b, is this correct? Does it even matter which size you use?
4) The Huffman encoding =)
should I just use ordinary Huffman on an uncompressed DCList file?
---
uncompressed DCList =
dirname
<tab>dirname
<tab><filename>file|size
---
I've had a look at the DC src at it seems to write checksums in the beginning of the file?
thanks in advance!
-
- DC++ Contributor
- Posts: 3212
- Joined: 2003-01-07 21:46
- Location: .pa.us
Re: Client <-> protocol questions
I'm not the DC protocol expert, but two questions below don't relate, so I'll touch on them.
[the appropriate place to look in DC++ is client/BufferedSocket.cpp BufferedSocket::threadSendFile for the uploading code]
Isn't $ the separator for arguments? The real question should be what does the 1 stand for, shouldn't it?zhaPP wrote:1) In the client to client protocol, why is a "$1" added to the filename in the $Get command? I get like this: "$Get MyList.DcLst$1|" ?
Yes, ACKs are part of the TCP/IP protocol.2) Is dc using any ack packets when sending files? I've done some sniffing and it seems like a data-packet always is followed by a packet from the client that receives the data.. Is this a feature of DC or just a part of the tcp/ip protocol?
[the appropriate place to look in DC++ is client/BufferedSocket.cpp BufferedSocket::threadSendFile for the uploading code]
Well, the maximum ethernet frame size is 1518 or 1522 (extended for a VLAN tag in 1998). You are likely seeing the effects of the MTU settings of various network interfaces between you and the target computer. Search on "MTU Path Discovery" on the internet. The OS does this automatically.3) What size should the data-packets be? DC++ seems to use 1460b, is this correct? Does it even matter which size you use?
1) it's the offset of the file (1=beginning, not 0)
2/3) yes, just a tcp stream.. I believe it does matter somewhat how much you send per send(2), but it's not something you have to worry about because it will only influence the speeds.
4) look at CryptoManager.cpp =)
and, look at http://wza.digitalbrains.com/DC/doc/
2/3) yes, just a tcp stream.. I believe it does matter somewhat how much you send per send(2), but it's not something you have to worry about because it will only influence the speeds.
4) look at CryptoManager.cpp =)
and, look at http://wza.digitalbrains.com/DC/doc/
http://dc.selwerd.nl/hublist.xml.bz2
http://www.b.ali.btinternet.co.uk/DCPlusPlus/index.html (TheParanoidOne's DC++ Guide)
http://www.dslreports.com/faq/dc (BSOD2600's Direct Connect FAQ)
http://www.b.ali.btinternet.co.uk/DCPlusPlus/index.html (TheParanoidOne's DC++ Guide)
http://www.dslreports.com/faq/dc (BSOD2600's Direct Connect FAQ)
ahh.. that explains everything.. thanks.Sedulus wrote:1) it's the offset of the file (1=beginning, not 0)
ok.. will try thatSedulus wrote:2/3) yes, just a tcp stream.. I believe it does matter somewhat how much you send per send(2), but it's not something you have to worry about because it will only influence the speeds.
hehe.. I've had a look at that one, without understanding any more..Sedulus wrote:4) look at CryptoManager.cpp =)
thanks for the help..
You know huffman coding? Here are the details for the neomodus variant:
byte offset / description
0 = "HE3" (magic bytes)
3 = 13
4 = checksum (XOR of every byte in the uncompressed file)
5 = length of uncompressed file
9 = number of entries in following table
11 = entry 1 [uncompressed byte, compressed code length (in bits)]
13 = entry 2 [uncompressed byte, compressed code length (in bits)]
...
(directly after end of table) = variable length huffman codes
Hope this helps
byte offset / description
0 = "HE3" (magic bytes)
3 = 13
4 = checksum (XOR of every byte in the uncompressed file)
5 = length of uncompressed file
9 = number of entries in following table
11 = entry 1 [uncompressed byte, compressed code length (in bits)]
13 = entry 2 [uncompressed byte, compressed code length (in bits)]
...
(directly after end of table) = variable length huffman codes
Hope this helps
do you just huffman-compress the raw filelist and add the header in the beginning?Gumboot wrote:You know huffman coding? Here are the details for the neomodus variant:
byte offset / description
0 = "HE3" (magic bytes)
3 = 13
4 = checksum (XOR of every byte in the uncompressed file)
5 = length of uncompressed file
9 = number of entries in following table
11 = entry 1 [uncompressed byte, compressed code length (in bits)]
13 = entry 2 [uncompressed byte, compressed code length (in bits)]
...
(directly after end of table) = variable length huffman codes
Hope this helps
Since you asked so nicely, here's my huffman decoder.
It's written in C# and IIRC has been tested and verified as working. YMMV. If you find any bugs please contact me.
It's written in C# and IIRC has been tested and verified as working. YMMV. If you find any bugs please contact me.
Code: Select all
/// <summary>
/// Class for decoding NMDC file lists (of the form MyList.DcLst).
/// Encoding is not supported.
/// </summary>
/// <remarks>
/// Written by Paul Bartrum ([email protected])
/// </remarks>
public class NMDCHuffmanStream : Stream
{
Stream baseStream;
BitReader bitReader;
int precomputedParity;
int calculatedParity;
int dataLength; // Uncompressed data length.
class HuffmanNode
{
public HuffmanNode Zero;
public HuffmanNode One;
public byte Symbol;
}
HuffmanNode root;
int position;
public NMDCHuffmanStream(Stream baseStream)
{
this.baseStream = baseStream;
if (baseStream.CanRead)
InitReader();
else
throw new NotSupportedException("This stream supports read-access only.");
}
private void InitReader()
{
BinaryReader reader = new BinaryReader(baseStream);
// Read magic bytes "HE3" + 13
byte[] magic = reader.ReadBytes(4);
if (magic[0] != (int) 'H' || magic[1] != (int) 'E' ||
magic[2] != (int) '3' || magic[3] != 13)
throw new IOException("The specified stream is not HE3 (huffman) compressed.");
// Read checksum byte. This is a XOR of every byte in the uncompressed file.
this.precomputedParity = reader.ReadByte();
// Read the length of the uncompressed file.
this.dataLength = reader.ReadInt32();
// Read an array of [uncompressed byte, compressed code length (in bits)].
int numCouples = reader.ReadInt16();
byte[,] couples = new byte[numCouples, 2];
for (int i = 0; i < numCouples; i ++)
{
couples[i,0] = reader.ReadByte(); // Uncompressed byte (symbol).
couples[i,1] = reader.ReadByte(); // Compressed code length.
}
// Init the bit reader.
this.bitReader = new BitReader(this.baseStream);
this.bitReader.HighToLow = false; // Bits are read from least
// significant to most significant.
// Construct the huffman tree.
this.root = new HuffmanNode();
for (int i = 0; i < numCouples; i ++)
{
HuffmanNode node = this.root;
for (int j = 0; j < couples[i,1]; j ++)
{
// Check the validity of the tree.
if (node.Symbol != 0)
throw new IOException("The huffman-compressed stream is corrupt.");
// Move the node to the zero or one nodes, depending on the value
// of the bit that is read.
if (bitReader.ReadBit())
{
if (node.One == null)
node.One = new HuffmanNode();
node = node.One;
}
else
{
if (node.Zero == null)
node.Zero = new HuffmanNode();
node = node.Zero;
}
}
// Check the validity of the tree.
if (node.Zero != null || node.One != null)
throw new IOException("The huffman-compressed stream is corrupt.");
// Store the uncompressed byte.
node.Symbol = couples[i,0];
}
// Ignore the rest of the bits in this byte.
bitReader.ReadBits(8 - bitReader.BitPosition);
}
public Stream BaseStream
{
get { return baseStream; }
}
public override void Close()
{
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Length
{
get { return dataLength; }
}
public override long Position
{
get { return position; }
set { throw new NotSupportedException(); }
}
public override int Read(byte[] buffer, int offset, int count)
{
// Make sure we don't read past the end.
count = Math.Min(count, dataLength - position);
HuffmanNode node = this.root;
for (int i = offset; i < offset + count; i ++)
{
// For each bit, branch to the left or right.
while (node.Symbol == 0)
{
if (bitReader.ReadBit())
node = node.One;
else
node = node.Zero;
}
// Store the uncompressed symbol in the supplied buffer.
buffer[i] = node.Symbol;
calculatedParity ^= node.Symbol; // Update the parity.
// Start at the beginning again.
node = this.root;
}
// Update the position and return the number of bytes that were read.
position += count;
return count;
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Flush()
{
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
}
/// <summary>
/// Reads bits from a stream.
/// </summary>
public class BitReader
{
Stream input;
// Settings.
bool highToLow;
bool throwOnEOF;
byte cachedByte;
int bitPos;
bool eof;
public BitReader(Stream input)
{
this.input = input;
this.highToLow = true;
this.throwOnEOF = true;
this.cachedByte = 0;
this.bitPos = 8;
this.eof = false;
}
public bool HighToLow
{
get { return highToLow; }
set { highToLow = value; }
}
public bool ThrowOnEOF
{
get { return throwOnEOF; }
set { throwOnEOF = value; }
}
// The position in the current byte (0 - 8).
// Zero means that none of the bits in the current byte have been read.
public int BitPosition
{
get { return bitPos; }
}
public bool ReadBit()
{
return ReadBits(1) != 0;
}
public int ReadBits(int numBits)
{
if (numBits < 0 || numBits > 31)
throw new ArgumentOutOfRangeException("numBits");
if (eof)
return -1;
int result = 0;
while (numBits > 0)
{
if (this.bitPos == 8)
{
// Read another byte.
int b = input.ReadByte();
if (b == -1)
{
eof = true;
if (throwOnEOF)
throw new EndOfStreamException();
else
this.cachedByte = 0; // Pad with zeros.
}
else
this.cachedByte = (byte) b;
this.bitPos = 0;
}
int partialNumBits = Math.Min(numBits, 8 - this.bitPos);
result |= GetByteBits(this.cachedByte, this.bitPos, partialNumBits, highToLow) <<
(numBits - partialNumBits);
this.bitPos += partialNumBits;
numBits -= partialNumBits;
}
return result;
}
public static int GetByteBits(byte value, int start, int length, bool highToLow)
{
if (highToLow)
start = 8 - start - length; // Big-endian bit order.
return (value & (((1 << (start + length)) - 1) & ~((1 << start) - 1)))
>> start;
}
}
First: thanks to you, Gumboot...I'm playing around with the DC protocol at the moment and got caught at this DcLst decoding. Your source code has helped me understanding how this is done.
I wanted to be able to decompress DcLst files in my linux console, and because I haven't found a tool for this task, I decided to write one myself. Since the postings here helped me a lot, I thought it would just be fair to publish my results here where they might help someone else someday...
This C code has been successfully compiled with gcc / linux (and until now, it seems to work perfectly well), but I think it should be no problem to port it for example to windows...:
I wanted to be able to decompress DcLst files in my linux console, and because I haven't found a tool for this task, I decided to write one myself. Since the postings here helped me a lot, I thought it would just be fair to publish my results here where they might help someone else someday...
This C code has been successfully compiled with gcc / linux (and until now, it seems to work perfectly well), but I think it should be no problem to port it for example to windows...:
Code: Select all
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int bitreader(char * st,int bp);
struct hnode {
int zero;
int one;
char symbol;
} node[514];
int main(int argc,char *argv[])
{
if(argc<3) {
printf("DcLst decompressor\n");
printf("coded 2003 by HooMair\n\n");
printf("Syntax: %s <dclst-filename> <output-filename>\n",argv[0]);
return 0;
}
char is[9000000];
int flen,i,f;
f=fopen(argv[1],"r");
printf("file opened\n");
flen=0;
while(!feof(f)) {
is[flen]=fgetc(f);
flen++;
}
fclose(f);
printf("%i bytes read from %s\n",flen-1,argv[1]);
if(is[0]!='H' || is[1]!='E' || is[2]!='3' ||is[3]!=13) {
printf("ERROR: this is not a DcLst file!\n");
return 0;
} else {
printf("File correctly identified as DcLst file\n");
}
int size;
size = *(int*)&is[5];
printf("uncompressed file size: %i\n",size);
short numcouples;
numcouples = *(short*)&is[9];
printf("%i different encoded bytes found\n",numcouples);
unsigned char couples[256][2];
int pos=11;
int nodecount=2,bitpos=0,j,currentnode=1;
for(i=0;i<numcouples;i++) {
couples[i][0]=is[pos];
pos++;
couples[i][1]=is[pos];
pos++;
}
for(i=0;i<514;i++) {
node[i].zero=0;
node[i].one=0;
node[i].symbol=0;
}
bitpos=pos*8;
for(i=0;i<numcouples;i++) {
for(j=0;j<couples[i][1];j++) {
if(node[currentnode].symbol!=0) {
printf("error building huffman tree at node %i after %i nodes!\n",currentnode,nodecount);
return 0;
}
if(bitreader(is,bitpos)==0) {
if(node[currentnode].zero==0) {
node[currentnode].zero=nodecount;
currentnode=nodecount;
nodecount++;
} else {
currentnode=node[currentnode].zero;
}
} else {
if(node[currentnode].one==0) {
node[currentnode].one=nodecount;
currentnode=nodecount;
nodecount++;
} else {
currentnode=node[currentnode].one;
}
}
bitpos++;
}
if(node[currentnode].zero!=0 || node[currentnode].one!=0) {
printf("error building huffman tree at node %i!\n",currentnode);
return 0;
}
node[currentnode].symbol=couples[i][0];
currentnode=1;
}
bitpos=bitpos+(8-(bitpos%8));
printf("huffman tree building successful: %i nodes created\n",nodecount);
printf("decompression started...\n");
f=fopen(argv[2],"w");
int count=0;
do {
currentnode=1;
while(node[currentnode].symbol==0) {
if(bitreader(is,bitpos)==0) {
currentnode=node[currentnode].zero;
} else {
currentnode=node[currentnode].one;
}
bitpos++;
}
fputc(node[currentnode].symbol,f);
count++;
} while(count<size);
fclose(f);
printf("done!\n");
}
int bitreader(char * st,int bp)
{
int charnum,bitnum;
charnum=(bp-(bp%8))/8;
bitnum=bp%8;
if((st[charnum] & (1<<bitnum))==0) return 0; else return 1;
}
Well OK then. Same priviso as above; mail me if you find any bugs.
Code: Select all
/// <summary>
/// Class for compressing NMDC file lists.
/// </summary>
/// <remarks>
/// Written by Paul Bartrum ([email protected])
/// </remarks>
public class NMDCHuffmanCompressor
{
class HuffmanCompressionNode
{
public HuffmanCompressionNode Parent;
public HuffmanCompressionNode Zero;
public HuffmanCompressionNode One;
public int Frequency;
public int Symbol;
}
struct HuffmanTableEntry
{
public int Code;
public int CodeLength;
}
public static void Compress(Stream inputStream, Stream outputStream)
{
byte[] inputBuffer = new byte[inputStream.Length];
inputStream.Read(inputBuffer, 0, inputBuffer.Length);
Compress(inputBuffer, outputStream);
}
public static void Compress(byte[] inputBuffer, Stream outputStream)
{
// Construct a frequency table and calculate the parity.
int[] frequencyTable = new int[256];
byte parity = 0;
foreach (byte b in inputBuffer)
{
frequencyTable[b] ++;
parity ^= b;
}
// Create a node for each symbol.
HuffmanCompressionNode[] leaves = new HuffmanCompressionNode[256];
for (int i = 0; i < 256; i ++)
{
// Ignore symbols that don't appear in the data.
if (frequencyTable[i] == 0)
continue;
// Create a new node.
HuffmanCompressionNode node = new HuffmanCompressionNode();
node.Frequency = frequencyTable[i];
node.Symbol = i;
// Add the node to the list of leaf nodes.
leaves[i] = node;
}
System.Collections.ArrayList roots = new System.Collections.ArrayList(256);
// Initially, every leaf node is a root.
foreach (HuffmanCompressionNode leaf in leaves)
if (leaf != null)
roots.Add(leaf);
// Construct the huffman tree.
while (roots.Count > 1)
{
// Find the two lowest frequency roots.
int lowestFrequency = int.MaxValue;
HuffmanCompressionNode lowestFrequencyRoot = null;
int secondLowestFrequency = int.MaxValue;
HuffmanCompressionNode secondLowestFrequencyRoot = null;
foreach (HuffmanCompressionNode root in roots)
{
if (root.Frequency < lowestFrequency)
{
secondLowestFrequency = lowestFrequency;
secondLowestFrequencyRoot = lowestFrequencyRoot;
lowestFrequency = root.Frequency;
lowestFrequencyRoot = root;
}
else if (root.Frequency < secondLowestFrequency)
{
secondLowestFrequency = root.Frequency;
secondLowestFrequencyRoot = root;
}
}
// Remove the two lowest roots from the list of roots.
roots.Remove(lowestFrequencyRoot);
roots.Remove(secondLowestFrequencyRoot);
// Create the parent node.
HuffmanCompressionNode parent = new HuffmanCompressionNode();
parent.Frequency = lowestFrequencyRoot.Frequency + secondLowestFrequencyRoot.Frequency;
// Connect the dots. :-)
lowestFrequencyRoot.Parent = parent;
secondLowestFrequencyRoot.Parent = parent;
parent.Zero = lowestFrequencyRoot;
parent.One = secondLowestFrequencyRoot;
roots.Add(parent);
}
// Construct a table from the tree.
HuffmanTableEntry[] lookupTable = new HuffmanTableEntry[256];
int numNonZeroEntries = 0;
for (int i = 0; i < 256; i ++)
{
HuffmanCompressionNode node = leaves[i];
if (node == null)
continue;
while (node.Parent != null)
{
lookupTable[i].Code <<= 1;
if (node.Parent.One == node)
lookupTable[i].Code |= 1;
lookupTable[i].CodeLength ++;
node = node.Parent;
}
numNonZeroEntries ++;
}
// Write header.
BinaryWriter writer = new BinaryWriter(outputStream);
writer.Write(new byte[] { (byte) 'H', (byte) 'E', (byte) '3', 13 });
writer.Write((byte) parity);
writer.Write((int) inputBuffer.Length);
// Write couples.
writer.Write((short) numNonZeroEntries);
for (int i = 0; i < 256; i ++)
if (lookupTable[i].CodeLength > 0)
{
writer.Write((byte) i);
writer.Write((byte) lookupTable[i].CodeLength);
}
// Write codes.
BitWriter bitWriter = new BitWriter(outputStream);
bitWriter.HighToLow = false;
for (int i = 0; i < 256; i ++)
if (lookupTable[i].CodeLength > 0)
bitWriter.WriteBits(lookupTable[i].Code, lookupTable[i].CodeLength);
// Pad the rest of the byte.
bitWriter.Flush();
// Start writing data to the output stream.
foreach (byte b in inputBuffer)
bitWriter.WriteBits(lookupTable[b].Code, lookupTable[b].CodeLength);
bitWriter.Flush();
}
}
/// <summary>
/// Writes bits to a stream.
/// </summary>
public class BitWriter
{
Stream output;
// Settings.
bool highToLow;
byte cachedByte;
int bitPos;
public BitWriter(Stream output)
{
this.output = output;
this.highToLow = true;
this.cachedByte = 0;
this.bitPos = 0;
}
public bool HighToLow
{
get { return highToLow; }
set { highToLow = value; }
}
// The position in the current byte (0 - 8).
// Zero means that none of the bits in the current byte have been written.
public int BitPosition
{
get { return bitPos; }
}
public void WriteBit(bool value)
{
WriteBits(value ? 1 : 0, 1);
}
public void WriteBits(int value, int numBitsArg)
{
int numBits = numBitsArg;
if (numBits < 0 || numBits > 31)
throw new ArgumentOutOfRangeException("numBits");
while (numBits > 0)
{
int partialNumBits = Math.Min(numBits, 8 - this.bitPos);
if (highToLow)
this.cachedByte |= (byte) (GetUIntBits((uint) value, 32 - numBits, partialNumBits, true) <<
(8 - partialNumBits - this.bitPos));
else
this.cachedByte |= (byte) (GetUIntBits((uint) value, numBitsArg - numBits, partialNumBits, false) <<
this.bitPos);
this.bitPos += partialNumBits;
numBits -= partialNumBits;
if (this.bitPos == 8)
Flush();
}
}
public void Flush()
{
// Check if any data is cached.
if (this.bitPos == 0)
return;
// Write the cached data to the stream.
output.WriteByte(this.cachedByte);
this.bitPos = 0;
this.cachedByte = 0;
}
public static int GetUIntBits(uint value, int start, int length, bool highToLow)
{
if (highToLow)
start = 32 - start - length; // Big-endian bit order.
return (int) ((uint) (value & (((1 << (start + length)) - 1) & ~((1 << start) - 1))) >> start);
}
}
-
- The Creator Himself
- Posts: 296
- Joined: 2003-01-02 17:15
Totally lame, untested and non-compressing PHP version of the HE3 encoder
(It should work without much hassle at least...
param: your plain text list as a string
returns: the encoded string
(It should work without much hassle at least...
param: your plain text list as a string
returns: the encoded string
Code: Select all
function encode($in) {
$datalen = strlen($in);
$out = "HE3" . chr(0xD);
$checksum = 0;
if ($datalen == 0) return $out.chr(0) .chr(0).chr(0).chr(0).chr(0).chr(0).chr(0) ;
for ($i = 0; $i < $datalen; $i++) $checksum ^= substr($in,$i,1);
$out .= chr($checksum) . intval($datalen) . chr(255) . chr(0);
for ($i = 0; $i < 255; $i++) $out .= chr(8);
for ($i = 0; $i < 255; $i++) $out .= chr($i);
$out .= $in;
return $out;
}
I wrote QuickDC - A DC++ compatible client for Linux and FreeBSD.
Nah, I wrote the above in two minutes...futti wrote:Havn't got a decoder to?
I will be testing later to day or tomorrow
The decoder have to decompress if the data is compressed, thus cannot be a quick and dirty hack the same way as the encoder...
That being said, I remember someone trying to do a DC client in PHP before, look over at sourceforge. Maybe you'll find what you are looking for there?
I wrote QuickDC - A DC++ compatible client for Linux and FreeBSD.
-
- Forum Moderator
- Posts: 366
- Joined: 2004-03-06 02:46
The first byte is the character, and the second is the length of the code in bits. I would assume that the compression uses the same clever trick that DEFLATE uses in that bytes with the same code length have codes that come in the same order as the bytes. Ex. if 'A' and 'B' both have a code length of 3, then A's code would be 110 and B's would be 111 because 110 comes first. It's not quite as cool as DEFLATE in that it doesn't store the code lengths as Huffman codes, though.
I am using this very code in my DC client, using C/C++ & Visual Studio 2003 (under WinXP), but someone on my hub has a file list which crashes the client in the bitreader() function (charnum becomes > than strlen(st).. )HooMair wrote:This C code has been successfully compiled with gcc / linux (and until now, it seems to work perfectly well), but I think it should be no problem to port it for example to windows...:Code: Select all
#include<stdio> #include<stdlib> #include<string> int bitreader(char * st,int bp); struct hnode { int zero; int one; char symbol; } node[514]; [....code removed to preserve topic readability....]
Does someone have the same problem with the same file list? : http://tamaire.free.fr/-BoB-Wish.DcLst (446kiB)
Did someone ever had the same problem with another file list ?
Thanks for the help
-
- Forum Moderator
- Posts: 366
- Joined: 2004-03-06 02:46
-
- Posts: 506
- Joined: 2003-01-03 07:33
But, then, why don't you use the bz2-extension filelist instead of the xml.bz2 one. The bz2-extension without xml is extremly simple to parse. I would imagine all clients you try to connect to will support it.
Everyone is supposed to download from the hubs, - I don´t know why, but I never do anymore.