A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 345 lines 11 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2017 Sebastian Leonhardt 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21 22#include "plugin.h" 23 24/** 25 * Follow Windows shortcuts (*.lnk files) in Rockbox. 26 * If the destination is a file, it will be selected in the file browser, 27 * a directory will be entered. 28 * For now, only relative links are supported. 29 */ 30 31/* a selection of link flags */ 32#define HAS_LINK_TARGET_ID_LIST 0x01 33#define HAS_LINK_INFO 0x02 34#define HAS_NAME 0x04 35#define HAS_RELATIVE_PATH 0x08 36#define HAS_WORKING_DIR 0x10 37#define HAS_ARGUMENTS 0x20 38#define HAS_ICON_LOCATION 0x40 39#define IS_UNICODE 0x80 40#define FORCE_NO_LINK_INFO 0x100 41/* a selection of file attributes flags */ 42#define FILE_ATTRIBUTE_DIRECTORY 0x10 43#define FILE_ATTRIBUTE_NORMAL 0x80 44 45 46/** 47 * Read one byte from file 48 * \param fd file descriptor 49 * \param *a where the data should go 50 * \return false if an error occured, true on success 51 */ 52static bool read_byte(const int fd, unsigned char *a) 53{ 54 if (!rb->read(fd, a, 1)) 55 return false; 56 return true; 57} 58 59/** 60 * Read 16-bit word from file, respecting windows endianness (little endian) 61 * \param fd file descriptor 62 * \param *a where the data should go 63 * \return false if an error occured, true on success 64 */ 65static bool read_word(const int fd, int *a) 66{ 67 unsigned char a1,a2; 68 int r; 69 70 r = read_byte(fd, &a1); 71 if (!r) 72 return false; 73 r = read_byte(fd, &a2); 74 if (!r) 75 return false; 76 *a = (a2<<8) + a1; 77 return true; 78} 79 80/** 81 * Read 32-bit word from file, respecting windows endianness (little endian) 82 * \param fd file descriptor 83 * \param *a where the data should go 84 * \return false if an error occured, true on success 85 */ 86static bool read_lword(const int fd, uint32_t *a) 87{ 88 int a1,a2; 89 int r; 90 91 r = read_word(fd, &a1); 92 if (!r) 93 return false; 94 r = read_word(fd, &a2); 95 if (!r) 96 return false; 97 *a = (a2<<16) + a1; 98 return true; 99} 100 101 102/** 103 * Scan *.lnk file for relative link target 104 * \param fd file descriptor 105 * \param link_target the extracted link destination 106 * \param target_size available space for the extracted link (in bytes) 107 * \param link_flags the link flags are stored here 108 * \param file_atts file attributes are stored here 109 * \return returns false if extraction failed. 110 */ 111static bool extract_link_destination(const int fd, 112 char *link_target, const int target_size, 113 uint32_t *link_flags, uint32_t *file_atts) 114{ 115 int r; 116 117 /* Read ShellLinkHeader */ 118 uint32_t size; 119 r = read_lword(fd, &size); 120 if (!r) return false; 121 if (size!=0x4c) { /* header size MUST be 76 bytes */ 122 DEBUGF("unexpected header size 0x%08x (must be 0x0000004c)\n", size); 123 return false; 124 } 125 126 /* Skip LinkCLSID (class identifier) */ 127 rb->lseek(fd, 0x10, SEEK_CUR); 128 /* We need the LinkFlags and File attribute (to see if target is a directory) */ 129 r = read_lword(fd, link_flags); 130 if (!r) return false; 131 r = read_lword(fd, file_atts); 132 if (!r) return false; 133 rb->lseek(fd, size, SEEK_SET); /* Skip to end of header */ 134 135 /* For now we only support relative links, so we can exit right away 136 if no relative link structure is present */ 137 if (!(*link_flags & HAS_RELATIVE_PATH)) { 138 DEBUGF("Link doesn't have relative path information\n"); 139 return false; 140 } 141 142 /* Read (skip) LinkTargetIDList structure if present */ 143 if (*link_flags & HAS_LINK_TARGET_ID_LIST) { 144 int size; 145 if (!read_word(fd, &size)) 146 return false; 147 rb->lseek(fd, size, SEEK_CUR); 148 } 149 150 /* Read (skip) LinkInfo structure if present */ 151 if (*link_flags & HAS_LINK_INFO) { 152 uint32_t size; 153 r = read_lword(fd, &size); 154 if (!r) return false; 155 rb->lseek(fd, size-4, SEEK_CUR); 156 } 157 158 /* String Data section */ 159 160 /* Read (skip) NAME_STRING StringData structure if present */ 161 if (*link_flags & HAS_NAME) { 162 int ccount; 163 if (!read_word(fd, &ccount)) 164 return false; 165 if (*link_flags & IS_UNICODE) 166 rb->lseek(fd, ccount*2, SEEK_CUR); 167 else 168 rb->lseek(fd, ccount, SEEK_CUR); 169 } 170 171 /* Read RELATIVE_PATH StringData structure if present */ 172 /* This is finally the data we are searching for! */ 173 if (*link_flags & HAS_RELATIVE_PATH) { 174 int ccount; 175 r = read_word(fd, &ccount); 176 if (!r) return false; 177 if (*link_flags & IS_UNICODE) { 178 unsigned char utf16[4], utf8[10]; 179 link_target[0] = '\0'; 180 for (int i=0; i<ccount; ++i) { 181 r = read_byte(fd, &utf16[0]); 182 if (!r) return false; 183 r = read_byte(fd, &utf16[1]); 184 if (!r) return false; 185 /* check for surrogate pair and read the second char */ 186 if (utf16[1] >= 0xD8 && utf16[1] < 0xE0) { 187 r = read_byte(fd, &utf16[2]); 188 if (!r) return false; 189 r = read_byte(fd, &utf16[3]); 190 if (!r) return false; 191 ++i; 192 } 193 char *ptr = rb->utf16LEdecode(utf16, utf8, 1); 194 *ptr = '\0'; 195 rb->strlcat(link_target, utf8, target_size); 196 } 197 } 198 else { /* non-unicode */ 199 if (ccount >= target_size) { 200 DEBUGF("ERROR: link target filename exceeds size!"); 201 return false; 202 } 203 rb->read(fd, link_target, ccount); 204 link_target[ccount] = '\0'; 205 } 206 } 207 208 /* convert from windows to unix subdir separators */ 209 for (int i=0; link_target[i] != '\0'; ++i) { 210 if (link_target[i]=='\\') 211 link_target[i] = '/'; 212 } 213 214 return true; 215} 216 217 218/** 219 * strip rightmost part of file/pathname to next '/', i.e. remove filename 220 * or last subdirectory. Leaves a trailing '/' character. 221 * \param pathname full path or filename 222*/ 223static void strip_rightmost_part(char *pathname) 224{ 225 for (int i = rb->strlen(pathname)-2; i >= 0; --i) { 226 if (pathname[i] == '/') { 227 pathname[i+1] = '\0'; /* cut off */ 228 return; 229 } 230 } 231 pathname[0] = '\0'; 232} 233 234 235/** 236 * Combine link file's absolute path with relative link target to form 237 * (absolute) link destination 238 * \param abs_path full shortcut filename (including path) 239 * \param rel_path the extracted relative link target 240 * \param max_len maximum lengt of combined filename 241 */ 242static void assemble_link_dest(char *const abs_path, char const *rel_path, 243 const size_t max_len) 244{ 245 strip_rightmost_part(abs_path); /* cut off link filename */ 246 247 for (;;) { 248 if (rb->strncmp(rel_path, "../", 3)==0) { 249 rel_path += 3; 250 strip_rightmost_part(abs_path); 251 } 252 else if (rb->strncmp(rel_path, "./", 2)==0) { 253 rel_path += 2; 254 } 255 else 256 break; 257 } 258 259 if (*rel_path=='/') 260 ++rel_path; /* avoid double '/' chars when concatenating */ 261 rb->strlcat(abs_path, rel_path, max_len); 262} 263 264 265/** 266 * Select the chosen file in the file browser. A directory (filename ending 267 * with '/') will be entered. 268 * \param link_target link target to be selected in the browser 269 * \return returns false if the target doesn't exist 270 */ 271static bool goto_entry(char *link_target) 272{ 273 DEBUGF("Trying to go to '%s'...\n", link_target); 274 if (!rb->file_exists(link_target)) 275 return false; 276 277 /* Set the browsers dirfilter to the global setting. 278 * This is required in case the plugin was launched 279 * from the plugins browser, in which case the 280 * dirfilter is set to only display .rock files */ 281 rb->set_dirfilter(rb->global_settings->dirfilter); 282 283 /* Change directory to the entry selected by the user */ 284 rb->set_current_file(link_target); 285 return true; 286} 287 288 289enum plugin_status plugin_start(const void* void_parameter) 290{ 291 char *link_filename; 292 char extracted_link[MAX_PATH]; 293 char link_target[MAX_PATH]; 294 uint32_t lnk_flags; 295 uint32_t file_atts; 296 297 /* This is a viewer, so a parameter must have been specified */ 298 if (void_parameter == NULL) { 299 rb->splash(HZ*3, "No *.lnk file selected"); 300 return PLUGIN_OK; 301 } 302 303 link_filename = (char*)void_parameter; 304 DEBUGF("Shortcut filename: \"%s\"\n", link_filename); 305 306 int fd = rb->open(link_filename, O_RDONLY); 307 if (fd < 0) { 308 DEBUGF("Can't open link file\n"); 309 rb->splashf(HZ*3, "Can't open link file!"); 310 return PLUGIN_OK; 311 } 312 313 if (!extract_link_destination(fd, extracted_link, sizeof(extracted_link), 314 &lnk_flags, &file_atts)) { 315 rb->close(fd); 316 DEBUGF("Error in extract_link_destination()\n"); 317 rb->splashf(HZ*3, "Unsupported or erroneous link file format"); 318 return PLUGIN_OK; 319 } 320 rb->close(fd); 321 DEBUGF("Shortcut destination: \"%s\"\n", extracted_link); 322 323 rb->strcpy(link_target, link_filename); 324 assemble_link_dest(link_target, extracted_link, sizeof(link_target)); 325 DEBUGF("Link absolute path: \"%s\"\n", link_target); 326 327 /* if target is a directory, add '/' to the dir name, 328 so that the directory gets entered instead of just highlighted */ 329 if (file_atts & FILE_ATTRIBUTE_DIRECTORY) 330 if (link_target[rb->strlen(link_target)-1] != '/') 331 rb->strlcat(link_target, "/", sizeof(link_target)); 332 333 if (!goto_entry(link_target)) { 334 char *what; 335 if (file_atts & FILE_ATTRIBUTE_DIRECTORY) 336 what = "directory"; 337 else 338 what = "file"; 339 rb->splashf(HZ*3, "Can't find %s %s", what, link_target); 340 DEBUGF("Can't find %s %s", what, link_target); 341 return PLUGIN_OK; 342 } 343 344 return PLUGIN_OK; 345}