1#!/usr/bin/perl
2#
3
4## This file is part of the aMule Project
5##
6## Copyright (c) 2004-2011 Angel Vidal ( kry@amule.org )
7## Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
8##
9## This program is free software; you can redistribute it and/or
10## modify it under the terms of the GNU General Public License
11## as published by the Free Software Foundation; either
12## version 2 of the License, or (at your option) any later version.
13##
14## This program is distributed in the hope that it will be useful,
15## but WITHOUT ANY WARRANTY; without even the implied warranty of
16## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17## GNU General Public License for more details.
18##
19## You should have received a copy of the GNU General Public License
20## along with this program; if not, write to the Free Software
21## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
22
23# Gimme a break, is my first perl app... (Kry)
24
25use File::Copy;
26use warnings;
27use strict;
28
29my $exit_with_help;
30
31if (!($ARGV[0])) {
32	print "You must specify the mldonkey config folder (usually ~/.mldonkey).\n";
33	$exit_with_help = "true";
34}
35
36if (!($ARGV[1])) {
37	print "You must specify the aMule temp folder for output.\n";
38	$exit_with_help = "true";
39}
40
41if ($exit_with_help) {
42	die "Usage: importer2.pl mldonkey_config_folder amule_temp_folder.\n";
43}
44
45
46my $input_folder = $ARGV[0];
47
48my $output_folder = $ARGV[1];
49
50open(TEST,">" . $output_folder . "/test_file") or die "Unable to write to destination folder! Error: $!\n";
51close(TEST);
52unlink($output_folder . "/test_file");
53
54open(INFO, $input_folder . "/files.ini") or die "Cannot open input file" . $input_folder . "/files.ini for reading: $!";		# Open the file
55
56my $line="no";
57while ($line !~ /^\s*files\s*=\s*\[\s*$/) {
58	$line = <INFO>;
59	if (!($line)) {
60		die $input_folder . "/files.ini seems not to be a mldonkey files.ini\n";
61	}
62	chop $line;
63}
64
65#We're at the start of the downloading files section.
66# Read info for each file.
67
68my $number = 1;
69
70while ($line && ($line !~ /^.*};\].*$/)) {
71	print "Reading info for file $number\n";
72	&read_file_info;
73	print "End reading\n\n";
74	$number++;
75}
76
77close(INFO);
78
79sub read_file_info {
80	$line = <INFO>;
81
82	my @md4_list = ();
83	my @gap_list = ();
84	my $file_size = 0;
85	my $file_name = "";
86	my $part_file = "";
87	my $md4_hash = "";
88
89	my $done = "false";
90
91	while (($line) && ($line !~ /^\s*}.*/) && ($done ne "true")) {
92		chop $line;
93		if ($line =~ /.*file_network\s*=\s*(.*)$/) {
94			print "Network is $1\n";
95			if ($1 ne "Donkey") {
96				print "Cannot import non-ed2k part file, skipping\n";
97				while (($line) && ($line !~ /^\s*}.*/)) {
98					$line = <INFO>;
99					$done = "true";
100				}
101			}
102		}
103		if ($line =~ /^\s*file_size\s*=\s*(\d+)\s*$/) {
104			$file_size = $1;
105			print "File size: $file_size\n";
106		}
107		if ($line =~ /^\s*file_swarmer\s*=\s*\"(.*)\"\s*$/) {
108			$part_file = $1;
109			print "Part file to import: $part_file\n";
110		}
111		if ($line =~ /^\s*file_md4\s*=\s*\"?(([A-Z]|[0-9])+)\"?\s*$/) {
112			$md4_hash = $1;
113			print "File hash: $md4_hash\n";
114		}
115		if ($line =~ /^\s*file_filename\s*=\s*\"(.*)\"\s*$/) {
116			$file_name = $1;
117			print "File name: $file_name\n";
118		}
119		if ($line =~ /^\s*file_md4s\s*=\s*\[\s*$/) {
120			# Read the MD4 list
121			my $result = "";
122			do {
123				my $md4_line = <INFO>;
124				if ($md4_line =~ /^\s*\"?(([A-Z]|[0-9])+)\"?;\]?\s*$/) {
125					push(@md4_list,$1);
126					if ($md4_line =~ /^.*;\].*$/) {
127						$result = "done";
128					}
129				} else {
130					print "Malformed md4 hash line $md4_line";
131					@md4_list = ();
132					$result = "error";
133				}
134			} while (!($result));
135			if ($result eq "done") {
136				print "MD4 list: @md4_list\n";
137			}
138
139
140		}
141
142		if ($line =~ /^\s*file_present_chunks\s*=\s*\[\s*$/) {
143			# Read the gaps list
144			my $result = "";
145			my @ml_gaps = ();
146			do {
147				my $gaps_line = <INFO>;
148				if ($gaps_line =~ /^\s*\((\d+),\s*(\d+)\)(;|])\s*$/) {
149					push(@ml_gaps,$1);
150					push(@ml_gaps,$2);
151					if ($gaps_line =~ /^.*\)\].*$/) {
152						$result = "done";
153					}
154				} else {
155					print "Malformed gaps line $gaps_line";
156					$result = "error";
157				}
158			} while (!($result));
159
160			if ($result eq "done") {
161				# Process mldonkey gaps to aMule gaps
162				print "ML Gaps list: @ml_gaps\n";
163
164				@gap_list = &convert_gap_format($file_size,@ml_gaps);
165
166				print "aMule Gaps list: @gap_list\n";
167			}
168
169
170		}
171
172		if ($done ne "true") {
173			$line = <INFO>;
174		}
175	}
176
177	if ($done eq "true") {
178		print "File import result: false\n";
179	} else {
180		if ($file_name && $file_size && $md4_hash && $part_file) {
181			if (!(@md4_list)) {
182				print "WARNING: File has no md4 hashes list, imported file will have 0 bytes downloaded\n";
183			}
184
185			my $first_free_number = &get_first_free_number;
186
187			my $met_file = $output_folder . sprintf("/%03d.part.met",$first_free_number);
188
189			&create_met_file($met_file,$file_name,$file_size,$md4_hash,@md4_list,"---",@gap_list);
190
191			print "File $met_file imported successfully.\n";
192
193			my $from = $input_folder . "/" . $part_file;
194			my $destination = $output_folder . sprintf("/%03d.part",$first_free_number);
195			copy($from, $destination) or die "CRITICAL: File $from cannot be copied to $destination. Error: $!\n";
196
197		} else {
198			print "Not enough info to import file, sorry.\n";
199		}
200	}
201	$line;
202}
203
204sub create_met_file {
205
206	print "Parameters: @_\n";
207
208	#Open the new file
209	open(MET," > $_[0]");
210	binmode MET;
211
212	my $large_file = "";
213
214	# Met file version (1 byte)
215	if ($_[2] < 4290048000) {
216		# Not large file
217		$large_file = "no";
218		printf MET &byte_string(0xe0);
219	} else {
220		$large_file = "yes";
221		printf MET &byte_string(0xe2);
222	}
223	# File modification time. 0 to force aMule rehash. (4 bytes)
224	print MET &int32_string(0);
225
226	# MD4 hash (16 bytes)
227	print MET &hash_string($_[3]);
228
229	#Calculate number of MD4 hashes
230	my @md4_hashlist = ();
231
232	my $i = 4;
233
234	while ($_[$i] ne "---") {
235		push (@md4_hashlist,$_[$i]);
236		$i++;
237	}
238
239	$i++;
240
241	my @gaps_list = ();
242	while ($_[$i]) {
243		push(@gaps_list,$_[$i]);
244		$i++;
245	}
246
247	print "Write aMule gap list: @gaps_list\n";
248
249	my $md4_hashsize = @md4_hashlist;
250
251	print "MD4 hashlist size $md4_hashsize\n";
252
253	#Number of MD4 hashes (2 bytes)
254	print MET &int16_string($md4_hashsize);
255
256	#Write MD4 hashes (16 bytes * number of hashes)
257	my $md4_parthash = "";
258	foreach $md4_parthash (@md4_hashlist) {
259		print MET &hash_string($md4_parthash);
260	}
261
262	#Number of tags (4 bytes)
263
264	my $tags_number = 2; # Fixed tags (Name + Size)
265
266	$tags_number = $tags_number + @gaps_list;
267
268	print MET &int32_string($tags_number);
269
270	#Name tag (x bytes)
271
272	print MET &tag_string(2,0,0x01,$_[1]); # Tagtype string, id FT_FILENAME, value
273
274	#Size tag (x bytes)
275
276	if ($large_file eq "yes") {
277		print MET &tag_string(0x0b,0,0x02,$_[2]); # Tagtype UINT64, id FT_FILESIZE, value
278	} else {
279		print MET &tag_string(3,0,0x02,$_[2]); # Tagtype UINT32, id FT_FILESIZE, value
280	}
281
282	my $t = 0;
283
284	my $tag_type;
285	if ($large_file eq "yes") {
286		$tag_type = 0x0b;
287	} else {
288		$tag_type = 0x03;
289	}
290
291	while (@gaps_list[$t*2]) {
292		my $gap_start = @gaps_list[$t*2];
293		my $gap_end = @gaps_list[$t*2+1];
294
295		print "Gap $t start $gap_start end $gap_end\n";
296
297		print MET &tag_string($tag_type,1,sprintf("%c%d",0x09,$t),$gap_start);
298		print MET &tag_string($tag_type,1,sprintf("%c%d",0x0a,$t),$gap_end);
299
300		$t++;
301	}
302
303	close(MET);
304}
305
306sub byte_string {
307	sprintf("%c",$_[0]);
308}
309
310sub int16_string {
311	&byte_string($_[0] % 256) . &byte_string($_[0] / 256);
312}
313
314sub int32_string {
315	&int16_string($_[0] % 65536) . &int16_string($_[0] / 65536);
316}
317
318sub int64_string {
319	&int32_string($_[0] % 4294967296) . &int32_string($_[0] / 4294967296);
320}
321
322sub hash_string {
323	my $i = 0;
324	my $final_string = "";
325	while ($i < 32) {
326		$final_string = $final_string . &byte_string(hex(substr($_[0],$i,2)));
327		$i += 2;
328	}
329	$final_string;
330}
331
332sub tag_string {
333	# ONLY STRINGS AND UINT32/64 SUPPORTED
334
335	my $final_string = "";
336
337	# Tag type
338	$final_string = $final_string . &byte_string($_[0]);
339
340	if ($_[1] == 0) {
341		# Byte ID tag
342		$final_string = $final_string . &int16_string(1);
343		$final_string = $final_string . &byte_string($_[2]);
344	} else {
345		# String ID tag
346		$final_string = $final_string . &int16_string(length $_[2]) . $_[2];
347	}
348
349	if ($_[0] == 2) {
350		$final_string = $final_string . &int16_string(length $_[3]) . $_[3];
351	} else {
352		if ($_[0] == 3) {
353			# UINT32
354			$final_string = $final_string . &int32_string($_[3]);
355		} else {
356			if ($_[0] == 0x0b) {
357				# UINT64
358				$final_string = $final_string . &int64_string($_[3]);
359			}
360		}
361	}
362	$final_string;
363}
364
365sub convert_gap_format {
366	my $total_size = $_[0];
367
368	my @converted_gaps = ();
369
370	my $n = 1;
371
372	if ($_[1] != 0) {
373		push(@converted_gaps,0);
374		push(@converted_gaps,$_[1]);
375	}
376
377	$n++;
378
379	while ($_[$n+1]) {
380		push(@converted_gaps,$_[$n]);
381		push(@converted_gaps,$_[$n+1]);
382		$n += 2;
383	}
384
385	if ($_[$n] != $total_size) {
386		push(@converted_gaps,$_[$n]);
387		push(@converted_gaps,$total_size);
388	}
389
390	@converted_gaps;
391}
392
393sub get_first_free_number {
394	my $n = 1;
395	my $result = 0;
396
397	while (!$result && !($n>999)) {
398		open(TEST, " <" . $output_folder . sprintf("/%03d.part.met",$n)) or $result=$n;
399		close(TEST);
400		$n++;
401	}
402
403	$result;
404}
405