Java 中的 Windows 快捷方式 (.lnk) 解析器?

发布于 2024-07-09 03:42:20 字数 584 浏览 9 评论 0原文

我目前正在使用 Win32ShellFolderManager2ShellFolder.getLinkLocation 来解析 Java 中的 Windows 快捷方式。 不幸的是,如果 Java 程序在 Vista 下作为服务运行,getLinkLocation 则不起作用。 具体来说,我收到一个异常,指出“无法获取 shell 文件夹 ID 列表”。

搜索网络确实会提到此错误消息,但总是与 JFileChooser 有关。 我没有使用 JFileChooser,我只需要将 .lnk 文件解析到其目的地。

有谁知道我可以使用用 Java 编写的 .lnk 文件的第 3 方解析器吗?

此后,我在此处找到了 .lnk 格式的非官方文档,但如果有人以前做过的话,我宁愿不必做这项工作,因为格式相当可怕。

I'm currently using Win32ShellFolderManager2 and ShellFolder.getLinkLocation to resolve windows shortcuts in Java. Unfortunately, if the Java program is running as a service under Vista, getLinkLocation, this does not work. Specifically, I get an exception stating "Could not get shell folder ID list".

Searching the web does turn up mentions of this error message, but always in connection with JFileChooser. I'm not using JFileChooser, I just need to resolve a .lnk file to its destination.

Does anyone know of a 3rd-party parser for .lnk files written in Java I could use?

I've since found unofficial documentation for the .lnk format here, but I'd rather not have to do the work if anyone has done it before, since the format is rather scary.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

聆听风音 2024-07-16 03:42:20

使用 mslinks

mslinks 是一个开源、维护良好、良好的已测试的项目,其作者投入了大量精力对快捷方式格式进行逆向工程。 考虑使用它来代替答案中列出的任何代码。

如果该库不适合您的需求,请继续阅读...

来自各种其他答案的代码

我组合了来自各种答案的一些代码,并添加了注释(一些解释以及到目前为止对每个贡献者的认可),其他检查文件魔法,快速测试以查看给定文件是否可能是有效链接(无需读取所有字节),修复如果文件太小并执行了一些操作,则抛出带有适当消息的 ParseException 而不是 ArrayIndexOutOfBoundsException一般清理。

来源此处(如果您有任何更改) ,将它们直接推送到 GitHub repo/项目

package org.stackoverflowusers.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://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java/672775#672775
 * Originally called LnkParser
 *
 * Written by: (the stack overflow 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://stackoverflow.com/users/675721/code-bling
 *   Network file support added by Stefan Cordes http://stackoverflow.com/users/81330/stefan-cordes
 *   Adapted by Sam Brightman http://stackoverflow.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);
    }

}

Use mslinks library

mslinks is an open source, well-maintained, well-tested project whose author has put considerable effort into reverse-engineering the shortcut format. Consider using it instead of any of the code listed in the answers.

If that library does not suit your needs, read on...

Code from various other answers

I've combined some of the code from the various answers, and added comments (some explanation as well as credit to each contributor so far), additional check on the file magic, a quick test to see if a given file might be a valid link (without reading all of the bytes), a fix to throw a ParseException with appropriate message instead of ArrayIndexOutOfBoundsException if the file is too small and did some general clean-up.

Source here (if you have any changes, push them right to the GitHub repo/project.

package org.stackoverflowusers.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://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java/672775#672775
 * Originally called LnkParser
 *
 * Written by: (the stack overflow 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://stackoverflow.com/users/675721/code-bling
 *   Network file support added by Stefan Cordes http://stackoverflow.com/users/81330/stefan-cordes
 *   Adapted by Sam Brightman http://stackoverflow.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);
    }

}
梦忆晨望 2024-07-16 03:42:20

Sam Brightman 的解决方案仅适用于本地文件。
我添加了对网络文件的支持:

  • Java 中的 Windows 快捷方式 (.lnk) 解析器?
  • http://code.google.com/p/8bits /downloads/detail?name=The_Windows_Shortcut_File_Format.pdf
  • http ://www.javafaq.nu/java-example-code-468.html

    公共类 LnkParser { 
    
      公共 LnkParser(文件 f) 抛出 IOException { 
          解析(f); 
      } 
    
      private boolean isDirectory; 
      私有布尔值 isLocal; 
    
      公共布尔 isDirectory() { 
          返回是目录; 
      } 
    
      私有字符串real_file; 
    
      公共字符串 getRealFilename() { 
          返回真实文件; 
      } 
    
      私有无效解析(文件f)抛出IOException { 
          // 将整个文件读入字节缓冲区 
          FileInputStream fin = new FileInputStream(f); 
          ByteArrayOutputStream 约 = new ByteArrayOutputStream(); 
          byte[] buff = 新字节[256]; 
          而(真){ 
              int n = fin.read(buff); 
              如果(n==-1){ 
                  休息; 
              } 
              回合.write(buff, 0, n); 
          } 
          fin.close(); 
          byte[]链接=bout.toByteArray(); 
    
          解析链接(链接); 
      } 
    
      私人无效parseLink(字节[]链接){ 
          // 获取标志字节 
          字节标志=链接[0x14]; 
    
          // 获取文件属性字节 
          最终 int file_atts_offset = 0x18; 
          字节 file_atts = 链接[file_atts_offset]; 
          字节 is_dir_mask = (字节)0x10; 
          if ((file_atts & is_dir_mask) > 0) { 
              目录=真; 
          } 别的 { 
              目录=假; 
          } 
    
          // 如果 shell 设置存在,则跳过它们 
          最终 int shell_offset = 0x4c; 
          最终字节 has_shell_mask = (byte)0x01; 
          int shell_len = 0; 
          if ((flags & has_shell_mask) > 0) { 
              // 加 2 表示长度标记本身 
              shell_len = bytes2short(link, shell_offset) + 2; 
          } 
    
          // 获取文件设置 
          int file_start = 0x4c + shell_len; 
    
          最终 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; 
          // 获取本地音量和本地系统值 
          //最终int localVolumeTable_offset_offset = 0x0C; 
          最终 int basename_offset_offset = 0x10; 
          最终 int networkVolumeTable_offset_offset = 0x14; 
          最终 int 最终名称_偏移_偏移 = 0x18; 
          int Finalname_offset = link[file_start + Finalname_offset_offset] + file_start; 
          String Finalname = getNullDelimitedString(link, Finalname_offset); 
          如果(是本地){ 
              int basename_offset = link[file_start + basename_offset_offset] + file_start; 
              String basename = getNullDelimitedString(link, basename_offset); 
              real_file = 基本名称 + 最终名称; 
          } 别的 { 
              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] 
                      + 网络卷表偏移量; 
              字符串 shareName = getNullDelimitedString(link, shareName_offset); 
              real_file = 共享名 + "\\" + 最终名称; 
          } 
      } 
    
      私有静态 String getNullDelimitedString(byte[] bytes, int off) { 
          int 长度 = 0; 
          // 计数字节直到空字符 (0) 
          而(真){ 
              if (字节[off + len] == 0) { 
                  休息; 
              } 
              伦++; 
          } 
          返回新字符串(字节,关闭,长度); 
      } 
    
      /* 
       * 将两个字节转换为一个短音符,这是小端字节序,因为它是 
       * 适用于仅限英特尔的操作系统。 
       */ 
      私有静态 int bytes2short(byte[] bytes, int off) { 
          返回 ((字节[off + 1] & 0xff) << 8) |   (字节[关闭]和0xff); 
      } 
    
      /** 
       * 返回实例变量'isLocal'的值。 
       * 
       * @return 返回 isLocal。 
       */ 
      公共布尔 isLocal() { 
          返回 isLocal; 
      } 
      } 
      

Sam Brightman's solution is for local files only.
I added support for Network files:

  • Windows shortcut (.lnk) parser in 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;
    }
    }
    
温柔戏命师 2024-07-16 03:42:20

我可以在 GitHub 上推荐这个存储库:

https://github.com/BlackOverlord666/mslinks

在那里我找到了一个创建快捷方式的简单解决方案:

ShellLink.createLink("path/to/existing/file.txt", "path/to/the/future/shortcut.lnk");

如果您想阅读快捷方式:

File shortcut = ...;
String pathToExistingFile = new ShellLink(shortcut).resolveTarget();

如果您想更改快捷方式的图标,请使用:

ShellLink sl = ...;
sl.setIconLocation("/path/to/icon/file");

您可以编辑快捷方式链接的大多数属性,例如工作目录、工具提示文本、图标、命令行参数、热键、创建指向的链接LAN 共享文件和目录 等等...

希望这对您有帮助:)

亲切的问候
乔苏亚·弗兰克

I can recommend this repository on GitHub:

https://github.com/BlackOverlord666/mslinks

There I've found a simple solution to create shortcuts:

ShellLink.createLink("path/to/existing/file.txt", "path/to/the/future/shortcut.lnk");

If you want to read shortcuts:

File shortcut = ...;
String pathToExistingFile = new ShellLink(shortcut).resolveTarget();

If you want to change the icon of the shortcut, use:

ShellLink sl = ...;
sl.setIconLocation("/path/to/icon/file");

You can edit most properties of the shortcutlink such as working directory, tooltip text, icon, command line arguments, hotkeys, create links to LAN shared files and directories and much more...

Hope this helps you :)

Kind regards
Josua Frank

宣告ˉ结束 2024-07-16 03:42:20

链接到的代码 plan9assembler 似乎只需稍作修改即可工作。 我认为这只是“& 0xff”,以防止当 bytebytes2short< 中将 byte 向上转换为 int 时进行符号扩展/code> 需要更改的功能。 我添加了 http://www.i2s-lab.com/ 中描述的功能Papers/The_Windows_Shortcut_File_Format.pdf 连接“路径名的最后部分”,尽管在实践中这似乎没有在我的示例中使用。 我没有向标头添加任何错误检查或处理网络共享。 这是我现在使用的:

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);
    }
}

The code plan9assembler linked to appears to work with minor modification. I think it's just the "& 0xff" to prevent sign extension when bytes are upcast to ints in the bytes2short function that need changing. I've added the functionality described in http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf to concatenate the "final part of the pathname" even though in practice this doesn't seem to be used in my examples. I've not added any error checking to the header or dealt with network shares. Here's what I'm using now:

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);
    }
}
翻了热茶 2024-07-16 03:42:20

我也在 Java 中的“.lnk”上工作过(现在没有时间)。 我的代码是 here

这有点混乱(一些测试垃圾),但本地和网络解析效果很好。 创建链接也已实现。 请测试并向我发送补丁。

解析示例:

Shortcut scut = Shortcut.loadShortcut(new File("C:\\t.lnk"));
System.out.println(scut.toString());

创建新链接:

Shortcut scut = new Shortcut(new File("C:\\temp"));
OutputStream os = new FileOutputStream("C:\\t.lnk");
os.write(scut.getBytes());
os.flush();
os.close();

I've also worked( now have no time for that) on '.lnk' in Java. My code is here

It's little messy( some testing trash) but local and network parsing works good. Creating links is implemented too. Please test and send me patches.

Parsing example:

Shortcut scut = Shortcut.loadShortcut(new File("C:\\t.lnk"));
System.out.println(scut.toString());

Creating new link:

Shortcut scut = new Shortcut(new File("C:\\temp"));
OutputStream os = new FileOutputStream("C:\\t.lnk");
os.write(scut.getBytes());
os.flush();
os.close();
[旋木] 2024-07-16 03:42:20

@Code Bling 的解决方案对于用户目录中的文件不适用于我。

例如“C:/Users/用户名/文件名.txt”。

原因是:在 The_Windows_Shortcut_File_Format.pdf

@Stefan Cordes 在第 6 页提到,它表示只有前 2 位对于卷信息很重要。
当卷信息的第一位为“0”时,所有其他位可能会被随机垃圾填充。

因此,如果涉及到:

isLocal = (file_location_info_flag & 2) == 0;

那么 file_location_info_flag 可能是“3”。
该文件仍然是本地文件,但是这行代码将 false 分配给 isLocal

所以我建议对@Code Bling的代码进行以下调整:

isLocal = (file_location_info_flag & 1) == 1;

The solution of @Code Bling does not work for me for Files in the User directory.

For Example "C:/Users/Username/Filename.txt".

The reason for that is: in The_Windows_Shortcut_File_Format.pdf

that was mentioned by @Stefan Cordes on page 6 it says that only the first 2 bits are important for volumes info.
All other bits might be filled with random garbage when the first bit of volumes info is "0".

So if it comes to:

isLocal = (file_location_info_flag & 2) == 0;

then file_location_info_flag might be "3".
This file is still local but this line of code assigns false to isLocal.

So i suggest the following adjustment to @Code Bling's code:

isLocal = (file_location_info_flag & 1) == 1;
南风起 2024-07-16 03:42:20

这段简短的代码确实有用...

但需要两个修复:

  • 改进了 isPotentialValidLink,如果名称不以“.lnk”结尾,则不会加载文件

     public static boolean isPotentialValidLink(final File file) { 
          最终 int 最小长度 = 0x64; 
          布尔值 isPotentiallyValid = false; 
          if (file.getName().toLowerCase().endsWith(".lnk")) 
              尝试(最终InputStream fis = new FileInputStream(文件)){ 
                  isPotentiallyValid = file.isFile() &&   fis.available()>=minimum_length&&   isMagicPresent(getBytes(fis, 32)); 
              } catch (异常 e) { 
                  // 忘了它 
              } 
          返回 isPotentiallyValid; 
        } 
      
  • 偏移量必须使用 32 位计算,而不仅仅是一个字节...

     Final int Finalname_offset = bytesToDword(link,file_start + Finalname_offset_offset) + file_start; 
       最终 int basename_offset = bytesToDword(link,file_start + basename_offset_offset) + file_start; 
      

This short code is really usefull...

But two fixes are needed:

  • the isPotentialValidLink improved not to load file if name doesn't end with ".lnk"

      public static boolean isPotentialValidLink(final File file) {
        final int minimum_length = 0x64;
        boolean isPotentiallyValid = false;
        if (file.getName().toLowerCase().endsWith(".lnk"))
            try (final InputStream fis = new FileInputStream(file)) {
                isPotentiallyValid = file.isFile() && fis.available() >= minimum_length && isMagicPresent(getBytes(fis, 32));
            } catch (Exception e) {
                // forget it
            }
        return isPotentiallyValid;
      }
    
  • the offset has to be computed with 32bits not only a byte...

     final int finalname_offset = bytesToDword(link,file_start + finalname_offset_offset) + file_start;
     final int basename_offset = bytesToDword(link,file_start + basename_offset_offset) + file_start;
    
你又不是我 2024-07-16 03:42:20

我发现了其他非专业技术。 完成我的工作。

File file=new File("C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\TeamViewer.lnk");//shortcut link
            FileInputStream stream=new FileInputStream(file);
            DataInputStream st=new DataInputStream(stream);
           byte[] bytes=new byte[(int)stream.getChannel().size()];
           stream.read(bytes);
           String data=new String(bytes);
           int i1=data.indexOf("C:\\Program Files");
           int i2=data.indexOf(".exe",i1);
           System.out.println(data.substring(i1, i2+4));

I found other non-professional technique. getting my job done.

File file=new File("C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\TeamViewer.lnk");//shortcut link
            FileInputStream stream=new FileInputStream(file);
            DataInputStream st=new DataInputStream(stream);
           byte[] bytes=new byte[(int)stream.getChannel().size()];
           stream.read(bytes);
           String data=new String(bytes);
           int i1=data.indexOf("C:\\Program Files");
           int i2=data.indexOf(".exe",i1);
           System.out.println(data.substring(i1, i2+4));
七堇年 2024-07-16 03:42:20

给定的代码运行良好,但有一个错误。 java字节是从-128到127的有符号值。我们想要从0到255的无符号值以获得正确的结果。 只需将 bytes2short 函数更改如下:

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;
}

The given code works well, but has a bug. A java byte is a signed value from -128 to 127. We want an unsigned value from 0 to 255 to get the correct results. Just change the bytes2short function as follows:

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文