tengo - Analizador de acceso directo de Windows(.lnk) en Java
tengo java actualizado (7)
El plan de ensamblador de plan9 vinculado parece funcionar con modificaciones menores. Creo que es solo el " & 0xff
" para evitar la extensión de la señal cuando los byte
se actualizan a int
en la función bytes2short
que deben cambiarse. He agregado la funcionalidad descrita en http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf para concatenar la "parte final de la ruta de acceso" aunque en la práctica esto no parece ser usado en mis ejemplos . No agregué ningún error al control del encabezado ni a los recursos compartidos de red. Esto es lo que estoy usando ahora:
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.text.DecimalFormat;
import java.text.NumberFormat;
public class LnkParser {
public LnkParser(File f) throws Exception {
parse(f);
}
private boolean is_dir;
public boolean isDirectory() {
return is_dir;
}
private String real_file;
public String getRealFilename() {
return real_file;
}
private void parse(File f) throws Exception {
// read the entire file into a byte buffer
FileInputStream fin = new FileInputStream(f);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buff = new byte[256];
while (true) {
int n = fin.read(buff);
if (n == -1) {
break;
}
bout.write(buff, 0, n);
}
fin.close();
byte[] link = bout.toByteArray();
// get the flags byte
byte flags = link[0x14];
// get the file attributes byte
final int file_atts_offset = 0x18;
byte file_atts = link[file_atts_offset];
byte is_dir_mask = (byte) 0x10;
if ((file_atts & is_dir_mask) > 0) {
is_dir = true;
} else {
is_dir = false;
}
// if the shell settings are present, skip them
final int shell_offset = 0x4c;
final byte has_shell_mask = (byte) 0x01;
int shell_len = 0;
if ((flags & has_shell_mask) > 0) {
// the plus 2 accounts for the length marker itself
shell_len = bytes2short(link, shell_offset) + 2;
}
// get to the file settings
int file_start = 0x4c + shell_len;
// get the local volume and local system values
final int basename_offset_offset = 0x10;
final int finalname_offset_offset = 0x18;
int basename_offset = link[file_start + basename_offset_offset]
+ file_start;
int finalname_offset = link[file_start + finalname_offset_offset]
+ file_start;
String basename = getNullDelimitedString(link, basename_offset);
String finalname = getNullDelimitedString(link, finalname_offset);
real_file = basename + finalname;
}
private static String getNullDelimitedString(byte[] bytes, int off) {
int len = 0;
// count bytes until the null character (0)
while (true) {
if (bytes[off + len] == 0) {
break;
}
len++;
}
return new String(bytes, off, len);
}
/*
* convert two bytes into a short note, this is little endian because it''s
* for an Intel only OS.
*/
private static int bytes2short(byte[] bytes, int off) {
return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
}
}
Actualmente estoy usando Win32ShellFolderManager2
y ShellFolder.getLinkLocation
para resolver los accesos directos de Windows en Java. Desafortunadamente, si el programa Java se ejecuta como un servicio bajo Vista, getLinkLocation
, esto no funciona. Específicamente, recibo una excepción que dice "No se pudo obtener la lista de ID de la carpeta del shell".
La búsqueda en la web aparece menciones a este mensaje de error, pero siempre en relación con JFileChooser
. No estoy usando JFileChooser
, solo necesito resolver un archivo .lnk
a su destino.
¿Alguien sabe de un analizador de terceros para archivos .lnk
escritos en Java que podría usar?
Desde entonces he encontrado documentación no oficial para el formato .lnk aquí , pero preferiría no tener que hacer el trabajo si alguien lo ha hecho antes, ya que el formato es bastante aterrador.
El código dado funciona bien, pero tiene un error. Un byte de Java es un valor firmado de -128 a 127. Queremos un valor sin signo de 0 a 255 para obtener los resultados correctos. Simplemente cambie la función bytes2short de la siguiente manera:
static int bytes2short(byte[] bytes, int off) {
int low = (bytes[off]<0 ? bytes[off]+256 : bytes[off]);
int high = (bytes[off+1]<0 ? bytes[off+1]+256 : bytes[off+1])<<8;
return 0 | low | high;
}
También trabajé (ahora no tengo tiempo para eso) en ''.lnk'' en Java. Mi código está aquí
Es un poco desordenado (algunas pruebas de basura) pero el análisis local y de red funciona bien. Crear enlaces también se implementa. Por favor, prueba y envíame parches.
Ejemplo de análisis:
Shortcut scut = Shortcut.loadShortcut(new File("C://t.lnk"));
System.out.println(scut.toString());
Creando un nuevo enlace:
Shortcut scut = new Shortcut(new File("C://temp"));
OutputStream os = new FileOutputStream("C://t.lnk");
os.write(scut.getBytes());
os.flush();
os.close();
Se agregaron comentarios (algunas explicaciones y crédito para cada colaborador hasta el momento), verificación adicional en el archivo magic, una prueba rápida para ver si un archivo determinado podría ser un enlace válido (sin leer todos los bytes), una solución para lanzar una ParseException con el mensaje apropiado en lugar de ArrayIndexOutOfBoundsException si el archivo es demasiado pequeño, realizó una limpieza general.
Fuente aquí (si tiene algún cambio, empújelo directamente al repositorio / proyecto de GitHub.
package org.users.file;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
/**
* Represents a Windows shortcut (typically visible to Java only as a ''.lnk'' file).
*
* Retrieved 2011-09-23 from http://.com/questions/309495/windows-shortcut-lnk-parser-in-java/672775#672775
* Originally called LnkParser
*
* Written by: (the users, obviously!)
* Apache Commons VFS dependency removed by crysxd (why were we using that!?) https://github.com/crysxd
* Headerified, refactored and commented by Code Bling http://.com/users/675721/code-bling
* Network file support added by Stefan Cordes http://.com/users/81330/stefan-cordes
* Adapted by Sam Brightman http://.com/users/2492/sam-brightman
* Based on information in ''The Windows Shortcut File Format'' by Jesse Hager <[email protected]>
* And somewhat based on code from the book ''Swing Hacks: Tips and Tools for Killer GUIs''
* by Joshua Marinacci and Chris Adamson
* ISBN: 0-596-00907-0
* http://www.oreilly.com/catalog/swinghks/
*/
public class WindowsShortcut
{
private boolean isDirectory;
private boolean isLocal;
private String real_file;
/**
* Provides a quick test to see if this could be a valid link !
* If you try to instantiate a new WindowShortcut and the link is not valid,
* Exceptions may be thrown and Exceptions are extremely slow to generate,
* therefore any code needing to loop through several files should first check this.
*
* @param file the potential link
* @return true if may be a link, false otherwise
* @throws IOException if an IOException is thrown while reading from the file
*/
public static boolean isPotentialValidLink(File file) throws IOException {
final int minimum_length = 0x64;
InputStream fis = new FileInputStream(file);
boolean isPotentiallyValid = false;
try {
isPotentiallyValid = file.isFile()
&& file.getName().toLowerCase().endsWith(".lnk")
&& fis.available() >= minimum_length
&& isMagicPresent(getBytes(fis, 32));
} finally {
fis.close();
}
return isPotentiallyValid;
}
public WindowsShortcut(File file) throws IOException, ParseException {
InputStream in = new FileInputStream(file);
try {
parseLink(getBytes(in));
} finally {
in.close();
}
}
/**
* @return the name of the filesystem object pointed to by this shortcut
*/
public String getRealFilename() {
return real_file;
}
/**
* Tests if the shortcut points to a local resource.
* @return true if the ''local'' bit is set in this shortcut, false otherwise
*/
public boolean isLocal() {
return isLocal;
}
/**
* Tests if the shortcut points to a directory.
* @return true if the ''directory'' bit is set in this shortcut, false otherwise
*/
public boolean isDirectory() {
return isDirectory;
}
/**
* Gets all the bytes from an InputStream
* @param in the InputStream from which to read bytes
* @return array of all the bytes contained in ''in''
* @throws IOException if an IOException is encountered while reading the data from the InputStream
*/
private static byte[] getBytes(InputStream in) throws IOException {
return getBytes(in, null);
}
/**
* Gets up to max bytes from an InputStream
* @param in the InputStream from which to read bytes
* @param max maximum number of bytes to read
* @return array of all the bytes contained in ''in''
* @throws IOException if an IOException is encountered while reading the data from the InputStream
*/
private static byte[] getBytes(InputStream in, Integer max) throws IOException {
// read the entire file into a byte buffer
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buff = new byte[256];
while (max == null || max > 0) {
int n = in.read(buff);
if (n == -1) {
break;
}
bout.write(buff, 0, n);
if (max != null)
max -= n;
}
in.close();
return bout.toByteArray();
}
private static boolean isMagicPresent(byte[] link) {
final int magic = 0x0000004C;
final int magic_offset = 0x00;
return link.length >= 32 && bytesToDword(link, magic_offset) == magic;
}
/**
* Gobbles up link data by parsing it and storing info in member fields
* @param link all the bytes from the .lnk file
*/
private void parseLink(byte[] link) throws ParseException {
try {
if (!isMagicPresent(link))
throw new ParseException("Invalid shortcut; magic is missing", 0);
// get the flags byte
byte flags = link[0x14];
// get the file attributes byte
final int file_atts_offset = 0x18;
byte file_atts = link[file_atts_offset];
byte is_dir_mask = (byte)0x10;
if ((file_atts & is_dir_mask) > 0) {
isDirectory = true;
} else {
isDirectory = false;
}
// if the shell settings are present, skip them
final int shell_offset = 0x4c;
final byte has_shell_mask = (byte)0x01;
int shell_len = 0;
if ((flags & has_shell_mask) > 0) {
// the plus 2 accounts for the length marker itself
shell_len = bytesToWord(link, shell_offset) + 2;
}
// get to the file settings
int file_start = 0x4c + shell_len;
final int file_location_info_flag_offset_offset = 0x08;
int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset];
isLocal = (file_location_info_flag & 2) == 0;
// get the local volume and local system values
//final int localVolumeTable_offset_offset = 0x0C;
final int basename_offset_offset = 0x10;
final int networkVolumeTable_offset_offset = 0x14;
final int finalname_offset_offset = 0x18;
int finalname_offset = link[file_start + finalname_offset_offset] + file_start;
String finalname = getNullDelimitedString(link, finalname_offset);
if (isLocal) {
int basename_offset = link[file_start + basename_offset_offset] + file_start;
String basename = getNullDelimitedString(link, basename_offset);
real_file = basename + finalname;
} else {
int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start;
int shareName_offset_offset = 0x08;
int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset]
+ networkVolumeTable_offset;
String shareName = getNullDelimitedString(link, shareName_offset);
real_file = shareName + "//" + finalname;
}
} catch (ArrayIndexOutOfBoundsException e) {
throw new ParseException("Could not be parsed, probably not a valid WindowsShortcut", 0);
}
}
private static String getNullDelimitedString(byte[] bytes, int off) {
int len = 0;
// count bytes until the null character (0)
while (true) {
if (bytes[off + len] == 0) {
break;
}
len++;
}
return new String(bytes, off, len);
}
/*
* convert two bytes into a short note, this is little endian because it''s
* for an Intel only OS.
*/
private static int bytesToWord(byte[] bytes, int off) {
return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
}
private static int bytesToDword(byte[] bytes, int off) {
return (bytesToWord(bytes, off + 2) << 16) | bytesToWord(bytes, off);
}
}
La solución de @Code Bling no funciona para mí para los archivos en el directorio de usuario.
Para el ejemplo "C: /Users/Username/Filename.txt".
La razón de esto es: en The_Windows_Shortcut_File_Format.pdf
que fue mencionado por @Stefan Cordes en la página 6 dice que solo los primeros 2 bits son importantes para la información de los volúmenes. Todos los demás bits pueden llenarse con basura al azar cuando el primer bit de información de volúmenes es "0".
Entonces, si se trata de:
isLocal = (file_location_info_flag & 2) == 0;
entonces file_location_info_flag
podría ser "3". Este archivo sigue siendo local pero esta línea de código asigna false
a isLocal
.
Así que sugiero el siguiente ajuste al código de @Code Bling:
isLocal = (file_location_info_flag & 1) == 1;
Puedo recomendar este repositorio en GitHub:
https://github.com/BlackOverlord666/mslinks
Allí encontré una solución simple para crear atajos:
ShellLink.createLink("path/to/existing/file.txt", "path/to/the/future/shortcut.lnk");
Si quieres leer accesos directos:
File shortcut = ...;
String pathToExistingFile = new ShellLink(shortcut).resolveTarget();
Si desea cambiar el ícono del acceso directo, use:
ShellLink sl = ...;
sl.setIconLocation("/path/to/icon/file");
Puede editar la mayoría de las propiedades del enlace abreviado, como el directorio de trabajo, el texto de información sobre herramientas, el icono, los argumentos de línea de comando, las teclas rápidas, crear enlaces a directorios y archivos compartidos de LAN y mucho más ...
Espero que esto te ayude :)
Saludos cordiales Josua Frank
La solución anterior es solo para archivos locales. Agregué soporte para archivos de red:
- Analizador de acceso directo de Windows (.lnk) en Java
- http://code.google.com/p/8bits/downloads/detail?name=The_Windows_Shortcut_File_Format.pdf
http://www.javafaq.nu/java-example-code-468.html
public class LnkParser { public LnkParser(File f) throws IOException { parse(f); } private boolean isDirectory; private boolean isLocal; public boolean isDirectory() { return isDirectory; } private String real_file; public String getRealFilename() { return real_file; } private void parse(File f) throws IOException { // read the entire file into a byte buffer FileInputStream fin = new FileInputStream(f); ByteArrayOutputStream bout = new ByteArrayOutputStream(); byte[] buff = new byte[256]; while (true) { int n = fin.read(buff); if (n == -1) { break; } bout.write(buff, 0, n); } fin.close(); byte[] link = bout.toByteArray(); parseLink(link); } private void parseLink(byte[] link) { // get the flags byte byte flags = link[0x14]; // get the file attributes byte final int file_atts_offset = 0x18; byte file_atts = link[file_atts_offset]; byte is_dir_mask = (byte)0x10; if ((file_atts & is_dir_mask) > 0) { isDirectory = true; } else { isDirectory = false; } // if the shell settings are present, skip them final int shell_offset = 0x4c; final byte has_shell_mask = (byte)0x01; int shell_len = 0; if ((flags & has_shell_mask) > 0) { // the plus 2 accounts for the length marker itself shell_len = bytes2short(link, shell_offset) + 2; } // get to the file settings int file_start = 0x4c + shell_len; final int file_location_info_flag_offset_offset = 0x08; int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset]; isLocal = (file_location_info_flag & 2) == 0; // get the local volume and local system values //final int localVolumeTable_offset_offset = 0x0C; final int basename_offset_offset = 0x10; final int networkVolumeTable_offset_offset = 0x14; final int finalname_offset_offset = 0x18; int finalname_offset = link[file_start + finalname_offset_offset] + file_start; String finalname = getNullDelimitedString(link, finalname_offset); if (isLocal) { int basename_offset = link[file_start + basename_offset_offset] + file_start; String basename = getNullDelimitedString(link, basename_offset); real_file = basename + finalname; } else { int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start; int shareName_offset_offset = 0x08; int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset] + networkVolumeTable_offset; String shareName = getNullDelimitedString(link, shareName_offset); real_file = shareName + "//" + finalname; } } private static String getNullDelimitedString(byte[] bytes, int off) { int len = 0; // count bytes until the null character (0) while (true) { if (bytes[off + len] == 0) { break; } len++; } return new String(bytes, off, len); } /* * convert two bytes into a short note, this is little endian because it''s * for an Intel only OS. */ private static int bytes2short(byte[] bytes, int off) { return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff); } /** * Returns the value of the instance variable ''isLocal''. * * @return Returns the isLocal. */ public boolean isLocal() { return isLocal; } }