#ifdef _WIN32 #include #else #include #endif #include #include #include #include #include #include #include #include #include "../../hash.h" #include "parser.h" #include "cd_detect.h" #include "../../compat/fnmatch_rarch.h" #include "../../file.h" #include "log.h" #define SHA1_LEN 40 #define HASH_LEN SHA1_LEN struct core_file { char name[PATH_MAX]; char desc[PATH_MAX]; char serial[PATH_MAX]; char size[PATH_MAX]; char sha1[PATH_MAX]; }; static int find_hash(int fd, const char *hash, char *game_name, size_t max_len, struct core_file *info_content) { if (!info_content) return -1; while (1) { if (find_token(fd, "game") < 0) return -1; if (find_token(fd, "name") < 0) return -1; if (get_token(fd, game_name, max_len) < 0) return -1; if (find_token(fd, "description") < 0) return -1; if (get_token(fd, info_content->desc, sizeof(info_content->desc)) < 0) return -1; if (find_token(fd, "size") < 0) return -1; if (get_token(fd, info_content->size, sizeof(info_content->size)) < 0) return -1; #if 0 if (find_token(fd, "serial") == 0) get_token(fd, info_content->serial, sizeof(info_content->serial)); #endif if (find_token(fd, "sha1") < 0) return -1; if (get_token(fd, info_content->sha1, MAX_TOKEN_LEN) < 0) return -1; if (!strcasecmp(hash, info_content->sha1)) break; } return 0; } static int find_content_canonical_name(const char *hash, char *game_name, size_t max_len, struct core_file *info_content) { // TODO: Error handling size_t i; int rv, fd, offs; const char *dat_name, *dat_name_dot; struct string_list *files = dir_list_new("db", "dat", false); if (!files) return -1; for (i = 0; i < files->size; i++) { dat_name = path_basename(files->elems[i].data); dat_name_dot = strchr(dat_name, '.'); if (!dat_name_dot) continue; offs = dat_name_dot - dat_name + 1; memcpy(game_name, dat_name, offs); fd = open(files->elems[i].data, O_RDONLY); if (fd < 0) continue; if (find_hash(fd, hash,game_name + offs, max_len - offs, info_content) == 0) { rv = 0; close(fd); goto clean; } close(fd); } rv = -1; clean: dir_list_free(files); return rv; } static int get_sha1(const char *path, char *result) { int rv; unsigned char buff[4096]; SHA1Context sha; int fd = open(path, O_RDONLY); if (fd < 0) return -errno; SHA1Reset(&sha); rv = 1; while (rv > 0) { rv = read(fd, buff, 4096); if (rv < 0) { close(fd); return -errno; } SHA1Input(&sha, buff, rv); } if (!SHA1Result(&sha)) { close(fd); return -1; } sprintf(result, "%08X%08X%08X%08X%08X", sha.Message_Digest[0], sha.Message_Digest[1], sha.Message_Digest[2], sha.Message_Digest[3], sha.Message_Digest[4]); close(fd); return 0; } struct RunInfo { char broken_cores[PATH_MAX]; int multitap; int dualanalog; char system[10]; }; static int read_launch_conf(struct RunInfo *info, const char *game_name) { int fd = open("./launch.conf", O_RDONLY); int rv; int bci = 0; char token[MAX_TOKEN_LEN]; if (fd < 0) return -errno; while (1) { if ((rv = get_token(fd, token, MAX_TOKEN_LEN)) < 0) goto clean; if (rl_fnmatch(token, game_name, 0) != 0) { if ((rv = find_token(fd, ";")) < 0) goto clean; continue; } LOG_DEBUG("Matched rule '%s'", token); break; } if ((rv = get_token(fd, token, MAX_TOKEN_LEN)) < 0) goto clean; while (strcmp(token, ";") != 0) { if (!strcmp(token, "multitap")) info->multitap = 1; else if (!strcmp(token, "dualanalog")) info->dualanalog = 1; else if (token[0] == '!') { strncpy(&info->broken_cores[bci], &token[1], PATH_MAX - bci); bci += strnlen(&token[1], PATH_MAX) + 1; } if ((rv = get_token(fd, token, MAX_TOKEN_LEN)) < 0) goto clean; } rv = 0; clean: close(fd); return rv; } static int get_run_info(struct RunInfo *info, const char *game_name) { int i; memset(info, 0, sizeof(struct RunInfo)); for (i = 0; i < 9; i++) { if (game_name[i] == '.') break; info->system[i] = game_name[i]; } info->system[i] = '\0'; info->multitap = 0; info->dualanalog = 0; read_launch_conf(info, game_name); return 0; } static int detect_content_game(const char *path, char *game_name, size_t max_len, struct core_file *info_content) { char hash[HASH_LEN + 1], *substr; const char *suffix = strrchr(path, '.'); if (!suffix) { LOG_WARN("Could not find extension for: %s", path); return -EINVAL; } memset(hash, 0, sizeof(hash)); get_sha1(path, hash); #if 0 if (rv < 0) LOG_WARN("Could not calculate hash: %s", strerror(-rv)); #endif if (find_content_canonical_name(hash, game_name, max_len, info_content) < 0) { LOG_DEBUG("Could not detect content with hash `%s`.", hash); return -EINVAL; } substr = strrchr(game_name, '.'); if (substr) strcpy(info_content->name, substr + 1); return 0; } int detect_file(const char *path, char *game_name, size_t max_len, struct core_file *info_content) { if ((!strcasecmp(path + strlen(path) - 4, ".cue")) || (!strcasecmp(path + strlen(path) - 4, ".m3u"))) { LOG_INFO("Starting CD game content detection..."); return detect_cd_game(path, game_name, max_len); } return detect_content_game(path, game_name, max_len, info_content); } #ifndef RARCH_CONSOLE int main(int argc, char *argv[]) { struct RunInfo info; struct core_file info_content; int rv; char game_name[MAX_TOKEN_LEN]; char *path = argv[1]; if (argc < 2) { printf("usage: retrolaunch \n"); return -1; } LOG_INFO("Analyzing '%s'", path); if ((rv = detect_file(path, game_name, MAX_TOKEN_LEN, &info_content)) < 0) { LOG_WARN("Could not detect game: %s", strerror(-rv)); return -1; } LOG_INFO("Content Name : `%s`", info_content.name); LOG_INFO("Content Description : `%s`", info_content.desc); LOG_INFO("Content Serial : `%s`", info_content.serial); LOG_INFO("Content Size : `%s`", info_content.size); LOG_INFO("Content SHA-1 : `%s`", info_content.sha1); if ((rv = get_run_info(&info, game_name)) < 0) { LOG_WARN("Could not detect run info: %s", strerror(-rv)); return -1; } return 0; } #endif