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