/* Extended Module Player * Copyright (C) 1996-2021 Claudio Matsuoka and Hipolito Carraro Jr * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #ifndef LIBXMP_CORE_PLAYER #if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #elif defined(__OS2__) || defined(__EMX__) #define INCL_DOS #define INCL_DOSERRORS #include #elif defined(__DJGPP__) #include #include #elif defined(HAVE_DIRENT_H) #include #endif #endif /* LIBXMP_CORE_PLAYER */ #include "xmp.h" #include "common.h" #include "period.h" #include "loader.h" int libxmp_init_instrument(struct module_data *m) { struct xmp_module *mod = &m->mod; if (mod->ins > 0) { mod->xxi = calloc(sizeof (struct xmp_instrument), mod->ins); if (mod->xxi == NULL) return -1; } if (mod->smp > 0) { int i; /* Sanity check */ if (mod->smp > MAX_SAMPLES) { D_(D_CRIT "sample count %d exceeds maximum (%d)", mod->smp, MAX_SAMPLES); return -1; } mod->xxs = calloc(sizeof (struct xmp_sample), mod->smp); if (mod->xxs == NULL) return -1; m->xtra = calloc(sizeof (struct extra_sample_data), mod->smp); if (m->xtra == NULL) return -1; for (i = 0; i < mod->smp; i++) { m->xtra[i].c5spd = m->c4rate; } } return 0; } /* Sample number adjustment (originally by Vitamin/CAIG). * Only use this AFTER a previous usage of libxmp_init_instrument, * and don't use this to free samples that have already been loaded. */ int libxmp_realloc_samples(struct module_data *m, int new_size) { struct xmp_module *mod = &m->mod; struct xmp_sample *xxs; struct extra_sample_data *xtra; /* Sanity check */ if (new_size < 0) return -1; if (new_size == 0) { /* Don't rely on implementation-defined realloc(x,0) behavior. */ mod->smp = 0; free(mod->xxs); mod->xxs = NULL; free(m->xtra); m->xtra = NULL; return 0; } xxs = realloc(mod->xxs, sizeof(struct xmp_sample) * new_size); if (xxs == NULL) return -1; mod->xxs = xxs; xtra = realloc(m->xtra, sizeof(struct extra_sample_data) * new_size); if (xtra == NULL) return -1; m->xtra = xtra; if (new_size > mod->smp) { int clear_size = new_size - mod->smp; int i; memset(xxs + mod->smp, 0, sizeof(struct xmp_sample) * clear_size); memset(xtra + mod->smp, 0, sizeof(struct extra_sample_data) * clear_size); for (i = mod->smp; i < new_size; i++) { m->xtra[i].c5spd = m->c4rate; } } mod->smp = new_size; return 0; } int libxmp_alloc_subinstrument(struct xmp_module *mod, int i, int num) { if (num == 0) return 0; mod->xxi[i].sub = calloc(sizeof (struct xmp_subinstrument), num); if (mod->xxi[i].sub == NULL) return -1; return 0; } int libxmp_init_pattern(struct xmp_module *mod) { mod->xxt = calloc(sizeof (struct xmp_track *), mod->trk); if (mod->xxt == NULL) return -1; mod->xxp = calloc(sizeof (struct xmp_pattern *), mod->pat); if (mod->xxp == NULL) return -1; return 0; } int libxmp_alloc_pattern(struct xmp_module *mod, int num) { /* Sanity check */ if (num < 0 || num >= mod->pat || mod->xxp[num] != NULL) return -1; mod->xxp[num] = calloc(1, sizeof (struct xmp_pattern) + sizeof (int) * (mod->chn - 1)); if (mod->xxp[num] == NULL) return -1; return 0; } int libxmp_alloc_track(struct xmp_module *mod, int num, int rows) { /* Sanity check */ if (num < 0 || num >= mod->trk || mod->xxt[num] != NULL || rows <= 0) return -1; mod->xxt[num] = calloc(sizeof (struct xmp_track) + sizeof (struct xmp_event) * (rows - 1), 1); if (mod->xxt[num] == NULL) return -1; mod->xxt[num]->rows = rows; return 0; } int libxmp_alloc_tracks_in_pattern(struct xmp_module *mod, int num) { int i; D_(D_INFO "Alloc %d tracks of %d rows", mod->chn, mod->xxp[num]->rows); for (i = 0; i < mod->chn; i++) { int t = num * mod->chn + i; int rows = mod->xxp[num]->rows; if (libxmp_alloc_track(mod, t, rows) < 0) return -1; mod->xxp[num]->index[i] = t; } return 0; } int libxmp_alloc_pattern_tracks(struct xmp_module *mod, int num, int rows) { /* Sanity check */ if (rows <= 0 || rows > 256) return -1; if (libxmp_alloc_pattern(mod, num) < 0) return -1; mod->xxp[num]->rows = rows; if (libxmp_alloc_tracks_in_pattern(mod, num) < 0) return -1; return 0; } /* Some formats explicitly allow more than 256 rows (e.g. OctaMED). This function * allows those formats to work without disrupting the sanity check for other formats. */ int libxmp_alloc_pattern_tracks_long(struct xmp_module *mod, int num, int rows) { /* Sanity check */ if (rows <= 0 || rows > 32768) return -1; if (libxmp_alloc_pattern(mod, num) < 0) return -1; mod->xxp[num]->rows = rows; if (libxmp_alloc_tracks_in_pattern(mod, num) < 0) return -1; return 0; } char *libxmp_instrument_name(struct xmp_module *mod, int i, uint8 *r, int n) { CLAMP(n, 0, 31); return libxmp_copy_adjust(mod->xxi[i].name, r, n); } char *libxmp_copy_adjust(char *s, uint8 *r, int n) { int i; memset(s, 0, n + 1); strncpy(s, (char *)r, n); for (i = 0; s[i] && i < n; i++) { if (!isprint((int)s[i]) || ((uint8)s[i] > 127)) s[i] = '.'; } while (*s && (s[strlen(s) - 1] == ' ')) s[strlen(s) - 1] = 0; return s; } void libxmp_read_title(HIO_HANDLE *f, char *t, int s) { uint8 buf[XMP_NAME_SIZE]; if (t == NULL) return; if (s >= XMP_NAME_SIZE) s = XMP_NAME_SIZE -1; memset(t, 0, s + 1); hio_read(buf, 1, s, f); /* coverity[check_return] */ buf[s] = 0; libxmp_copy_adjust(t, buf, s); } #ifndef LIBXMP_CORE_PLAYER int libxmp_test_name(uint8 *s, int n) { int i; for (i = 0; i < n; i++) { if (s[i] > 0x7f) return -1; /* ACS_Team2.mod has a backspace in instrument name */ if (s[i] > 0 && s[i] < 32 && s[i] != 0x08) return -1; } return 0; } int libxmp_copy_name_for_fopen(char *dest, const char *name, int n) { int converted_colon = 0; int i; /* libxmp_copy_adjust, but make sure the filename won't do anything * malicious when given to fopen. This should only be used on song files. */ if (!strcmp(name, ".") || strstr(name, "..") || name[0] == '\\' || name[0] == '/' || name[0] == ':') return -1; for (i = 0; i < n - 1; i++) { uint8 t = name[i]; if (!t) break; /* Reject non-ASCII symbols as they have poorly defined behavior. */ if (t < 32 || t >= 0x7f) return -1; /* Reject anything resembling a Windows-style root path. Allow * converting a single : to / so things like ST-01:samplename * work. (Leave the : as-is on Amiga.) */ if (i > 0 && t == ':' && !converted_colon) { uint8 t2 = name[i + 1]; if (!t2 || t2 == '/' || t2 == '\\') return -1; converted_colon = 1; #ifndef LIBXMP_AMIGA dest[i] = '/'; continue; #endif } if (t == '\\') { dest[i] = '/'; continue; } dest[i] = t; } dest[i] = '\0'; return 0; } /* * Honor Noisetracker effects: * * 0 - arpeggio * 1 - portamento up * 2 - portamento down * 3 - Tone-portamento * 4 - Vibrato * A - Slide volume * B - Position jump * C - Set volume * D - Pattern break * E - Set filter (keep the led off, please!) * F - Set speed (now up to $1F) * * Pex Tufvesson's notes from http://www.livet.se/mahoney/: * * Note that some of the modules will have bugs in the playback with all * known PC module players. This is due to that in many demos where I synced * events in the demo with the music, I used commands that these newer PC * module players erroneously interpret as "newer-version-trackers commands". * Which they aren't. */ void libxmp_decode_noisetracker_event(struct xmp_event *event, uint8 *mod_event) { int fxt; memset(event, 0, sizeof (struct xmp_event)); event->note = libxmp_period_to_note((LSN(mod_event[0]) << 8) + mod_event[1]); event->ins = ((MSN(mod_event[0]) << 4) | MSN(mod_event[2])); fxt = LSN(mod_event[2]); if (fxt <= 0x06 || (fxt >= 0x0a && fxt != 0x0e)) { event->fxt = fxt; event->fxp = mod_event[3]; } libxmp_disable_continue_fx(event); } #endif void libxmp_decode_protracker_event(struct xmp_event *event, uint8 *mod_event) { int fxt = LSN(mod_event[2]); memset(event, 0, sizeof (struct xmp_event)); event->note = libxmp_period_to_note((LSN(mod_event[0]) << 8) + mod_event[1]); event->ins = ((MSN(mod_event[0]) << 4) | MSN(mod_event[2])); if (fxt != 0x08) { event->fxt = fxt; event->fxp = mod_event[3]; } libxmp_disable_continue_fx(event); } void libxmp_disable_continue_fx(struct xmp_event *event) { if (event->fxp == 0) { switch (event->fxt) { case 0x05: event->fxt = 0x03; break; case 0x06: event->fxt = 0x04; break; case 0x01: case 0x02: case 0x0a: event->fxt = 0x00; } } else if (event->fxt == 0x0e) { if (event->fxp == 0xa0 || event->fxp == 0xb0) { event->fxt = event->fxp = 0; } } } #ifndef LIBXMP_CORE_PLAYER /* libxmp_check_filename_case(): */ /* Given a directory, see if file exists there, ignoring case */ #if defined(_WIN32) int libxmp_check_filename_case(const char *dir, const char *name, char *new_name, int size) { char path[_MAX_PATH]; DWORD rc; /* win32 is case-insensitive: directly probe the file. */ snprintf(path, sizeof(path), "%s/%s", dir, name); rc = GetFileAttributesA(path); if (rc == (DWORD)(-1)) return 0; if (rc & FILE_ATTRIBUTE_DIRECTORY) return 0; strncpy(new_name, name, size); return 1; } #elif defined(__OS2__) || defined(__EMX__) int libxmp_check_filename_case(const char *dir, const char *name, char *new_name, int size) { char path[CCHMAXPATH]; FILESTATUS3 fs; /* os/2 is case-insensitive: directly probe the file. */ snprintf(path, sizeof(path), "%s/%s", dir, name); if (DosQueryPathInfo(path, FIL_STANDARD, &fs, sizeof(fs)) != NO_ERROR) return 0; if (fs.attrFile & FILE_DIRECTORY) return 0; strncpy(new_name, name, size); return 1; } #elif defined(__DJGPP__) int libxmp_check_filename_case(const char *dir, const char *name, char *new_name, int size) { char path[256]; int attr; /* dos is case-insensitive: directly probe the file. */ snprintf(path, sizeof(path), "%s/%s", dir, name); attr = _chmod(path, 0); if (attr == -1) return 0; if (attr & (_A_SUBDIR|_A_VOLID)) return 0; strncpy(new_name, name, size); return 1; } #elif defined(HAVE_DIRENT_H) int libxmp_check_filename_case(const char *dir, const char *name, char *new_name, int size) { int found = 0; DIR *dirfd; struct dirent *d; dirfd = opendir(dir); if (dirfd == NULL) return 0; while ((d = readdir(dirfd)) != NULL) { if (!strcasecmp(d->d_name, name)) { found = 1; break; } } if (found) strncpy(new_name, d->d_name, size); closedir(dirfd); return found; } #else int libxmp_check_filename_case(const char *dir, const char *name, char *new_name, int size) { return 0; } #endif void libxmp_get_instrument_path(struct module_data *m, char *path, int size) { if (m->instrument_path) { strncpy(path, m->instrument_path, size); } else if (getenv("XMP_INSTRUMENT_PATH")) { strncpy(path, getenv("XMP_INSTRUMENT_PATH"), size); } else { strncpy(path, ".", size); } } #endif /* LIBXMP_CORE_PLAYER */ void libxmp_set_type(struct module_data *m, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsnprintf(m->mod.type, XMP_NAME_SIZE, fmt, ap); va_end(ap); } static int schism_tracker_date(int year, int month, int day) { int mm = (month + 9) % 12; int yy = year - mm / 10; yy = yy * 365 + (yy / 4) - (yy / 100) + (yy / 400); mm = (mm * 306 + 5) / 10; return yy + mm + (day - 1); } /* Generate a Schism Tracker version string. * Schism Tracker versions are stored as follows: * * s_ver <= 0x50: 0.s_ver * s_ver > 0x50, < 0xfff: days from epoch=(s_ver - 0x50) * s_ver = 0xfff: days from epoch=l_ver */ void libxmp_schism_tracker_string(char *buf, size_t size, int s_ver, int l_ver) { if (s_ver >= 0x50) { /* time_t epoch_sec = 1256947200; */ int t = schism_tracker_date(2009, 10, 31); int year, month, day, dayofyear; if (s_ver == 0xfff) { t += l_ver; } else t += s_ver - 0x50; /* Date algorithm reimplemented from OpenMPT. */ year = (int)(((int64)t * 10000L + 14780) / 3652425); dayofyear = t - (365 * year + (year / 4) - (year / 100) + (year / 400)); if (dayofyear < 0) { year--; dayofyear = t - (365 * year + (year / 4) - (year / 100) + (year / 400)); } month = (100 * dayofyear + 52) / 3060; day = dayofyear - (month * 306 + 5) / 10 + 1; year += (month + 2) / 12; month = (month + 2) % 12 + 1; snprintf(buf, size, "Schism Tracker %04d-%02d-%02d", year, month, day); } else { snprintf(buf, size, "Schism Tracker 0.%x", s_ver); } }