blob: ef837bc7783085f7100b464003cbb6ae873bc358 [file] [log] [blame]
tianruimingebb3dd02025-06-09 05:07:26 +00001package tracker;
2
3import java.io.File;
4import java.nio.file.Files;
5import java.security.MessageDigest;
6import java.util.Map;
7import com.dampcake.bencode.Bencode;
8import com.dampcake.bencode.Type;
9
10/**
11 * Enhanced InfoHash calculator that matches qBittorrent's calculation
12 *
13 * The key issues with infoHash calculation are:
14 * 1. Bencode libraries may encode data differently
15 * 2. The original torrent's info dictionary bytes should be preserved
16 * 3. Re-encoding might change the binary representation
17 */
18public class EnhancedInfoHashCalculator {
19
20 /**
21 * Calculate infoHash by extracting the original info dictionary bytes
22 * from the torrent file, rather than re-encoding the parsed data
23 */
24 public static String calculateInfoHash(File torrentFile) throws Exception {
25 byte[] torrentData = Files.readAllBytes(torrentFile.toPath());
26
27 // Find the info dictionary in the raw torrent data
28 // Look for the pattern "4:info" which indicates the start of the info dictionary
29 int infoStart = findInfoDictionary(torrentData);
30 if (infoStart == -1) {
31 throw new Exception("Could not find info dictionary in torrent file");
32 }
33
34 // Extract the info dictionary bytes directly from the original torrent
35 byte[] infoBytes = extractInfoBytes(torrentData, infoStart);
36
37 // Calculate SHA1 hash
38 MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
39 byte[] digest = sha1.digest(infoBytes);
40
41 // Convert to hex string
42 StringBuilder sb = new StringBuilder();
43 for (byte b : digest) {
44 sb.append(String.format("%02x", b & 0xff));
45 }
46
47 return sb.toString();
48 }
49
50 /**
51 * Find the position of "4:info" in the torrent data
52 */
53 private static int findInfoDictionary(byte[] data) {
54 byte[] pattern = "4:info".getBytes();
55
56 for (int i = 0; i <= data.length - pattern.length; i++) {
57 boolean found = true;
58 for (int j = 0; j < pattern.length; j++) {
59 if (data[i + j] != pattern[j]) {
60 found = false;
61 break;
62 }
63 }
64 if (found) {
65 return i;
66 }
67 }
68 return -1;
69 }
70
71 /**
72 * Extract the info dictionary bytes from the original torrent data
73 */
74 private static byte[] extractInfoBytes(byte[] data, int infoStart) throws Exception {
75 // Skip "4:info" to get to the actual dictionary content
76 int dictStart = infoStart + 6; // "4:info".length()
77
78 if (dictStart >= data.length || data[dictStart] != 'd') {
79 throw new Exception("Invalid info dictionary format");
80 }
81
82 // Find the matching 'e' that closes the info dictionary
83 int dictEnd = findMatchingEnd(data, dictStart);
84 if (dictEnd == -1) {
85 throw new Exception("Could not find end of info dictionary");
86 }
87
88 // Extract the info dictionary bytes (including 'd' and 'e')
89 int length = dictEnd - dictStart + 1;
90 byte[] infoBytes = new byte[length];
91 System.arraycopy(data, dictStart, infoBytes, 0, length);
92
93 return infoBytes;
94 }
95
96 /**
97 * Find the matching 'e' for a dictionary that starts with 'd'
98 */
99 private static int findMatchingEnd(byte[] data, int start) {
100 if (start >= data.length || data[start] != 'd') {
101 return -1;
102 }
103
104 int depth = 0;
105 for (int i = start; i < data.length; i++) {
106 if (data[i] == 'd' || data[i] == 'l') {
107 depth++;
108 } else if (data[i] == 'e') {
109 depth--;
110 if (depth == 0) {
111 return i;
112 }
113 }
114 }
115 return -1;
116 }
117
118 /**
119 * For comparison: calculate using the re-encoding method (your original approach)
120 */
121 public static String calculateInfoHashByReencoding(File torrentFile) throws Exception {
122 byte[] torrentData = Files.readAllBytes(torrentFile.toPath());
123 Bencode bencode = new Bencode();
124
125 @SuppressWarnings("unchecked")
126 Map<String,Object> meta = bencode.decode(torrentData, Type.DICTIONARY);
127 @SuppressWarnings("unchecked")
128 Map<String,Object> info = (Map<String,Object>) meta.get("info");
129
130 if (info == null) {
131 throw new Exception("No info dictionary found");
132 }
133
134 // Re-encode the info dictionary
135 byte[] infoBytes = bencode.encode(info);
136
137 // Calculate SHA1
138 MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
139 byte[] digest = sha1.digest(infoBytes);
140
141 StringBuilder sb = new StringBuilder();
142 for (byte b : digest) {
143 sb.append(String.format("%02x", b & 0xff));
144 }
145
146 return sb.toString();
147 }
148
149 public static void main(String[] args) {
150 if (args.length != 1) {
151 System.out.println("Usage: java EnhancedInfoHashCalculator <torrent-file>");
152 return;
153 }
154
155 File torrentFile = new File(args[0]);
156 if (!torrentFile.exists()) {
157 System.out.println("Torrent file not found: " + args[0]);
158 return;
159 }
160
161 try {
162 System.out.println("=== InfoHash Calculation Comparison ===");
163 System.out.println("File: " + torrentFile.getName());
164
165 String directHash = calculateInfoHash(torrentFile);
166 System.out.println("Direct extraction method: " + directHash);
167 System.out.println("Direct (uppercase): " + directHash.toUpperCase());
168
169 String reencodingHash = calculateInfoHashByReencoding(torrentFile);
170 System.out.println("Re-encoding method: " + reencodingHash);
171 System.out.println("Re-encoding (uppercase): " + reencodingHash.toUpperCase());
172
173 if (directHash.equals(reencodingHash)) {
174 System.out.println("✓ Both methods produce the same result");
175 } else {
176 System.out.println("✗ Methods produce different results!");
177 System.out.println("This suggests the Bencode re-encoding is changing the data");
178 }
179
180 } catch (Exception e) {
181 System.out.println("Error: " + e.getMessage());
182 e.printStackTrace();
183 }
184 }
185}