// SPDX-License-Identifier: GPL-2.0 /* .asd file format is the dive import/export format for Scubapro/Uwatec software * divelogs. It enables export from SmartTrak and import/export from LogTrak. * It's a binary format with remembrances to ancient DataTrak format in the way * strings are stored, and it keeps the samples from the DC as they are, so we can * simply pass them to libdivecomputer to parse. * The bad news: * - Dives aren't fixed size, thus we can't avoid a sequential parsing. * - There is a header area for each dive but this header is not the same * libdc expects as the expected headers are different based on DC model or family. * Thus faking a header as it's expected by libdc is a must. This has to be done per * dive, as a single file may have dives downloaded from different DCs. * - The byte sequence signaling the beginning of a new dive varies depending * on the origin of the file (SmartTrak or LogTrak). * - Files coming from SmartTrak can include dives imported from yet older * DataTrak software. I don't know if these dives coming from serial devices can * even be imported into LogTrak. ATM we can safely assume we are only finding * them in SmartTrak exported dives. * - ... Probably some more I just haven't found yet. */ #include #include #include #include #include #include "dive.h" #include "device.h" #include "errorhelper.h" #include "gettext.h" #include "divelist.h" #include "file.h" #include "libdivecomputer.h" #include "divesite.h" #include "equipment.h" #include "divelog.h" #include "tag.h" extern dc_status_t dt_libdc_buffer(unsigned char *ptr, int prf_length, int dc_model, unsigned char *compl_buffer); /* * dc model definitions */ #define SMARTPRO 0x10 #define GALILEO 0x11 #define ALADINTEC 0x12 #define ALADINTEC2G 0x13 #define SMARTCOM 0x14 #define ALADIN2G 0x15 #define ALADINSPORTMATRIX 0x17 #define SMARTTEC 0x18 #define GALILEOTRIMIX 0x19 #define SMARTZ 0x1C #define MERIDIAN 0x20 #define ALADINSQUARE 0x22 #define CHROMIS 0x24 #define ALADINA1 0x25 #define MANTIS2 0x26 #define ALADINA2 0x28 #define G2TEK 0x31 #define G2 0x32 #define G3 0x34 #define G2HUD 0x42 #define LUNA2AI 0x50 #define LUNA2 0x51 /* * Data positions in serial stream as expected by libdc */ #define LIBDC_MAX_DEPTH 22 #define LIBDC_DIVE_TIME 26 #define LIBDC_MAX_TEMP 28 #define LIBDC_MIN_TEMP 30 #define LIBDC_SURF_TEMP 32 #define LIBDC_GASMIX 44 #define LIBDC_TANK_PRESS 50 #define LIBDC_SETTINGS 92 #define LIBDC_MAX_DEPTH_SMART 18 #define LIBDC_DIVE_TIME_SMART 20 #define LIBDC_MIN_TEMP_SMART 22 #define LIBDC_GASMIX_SMART 24 #define LIBDC_GASMIX_SMARTZ 28 #define LIBDC_TANK_PRESS_SMARTZ 34 #define LIBDC_TANK_PRESS_SMARTCOM 30 #define LIBDC_DIVE_TIME_ALADINTEC 24 #define LIBDC_SETTINGS_ALADINTEC 52 #define LIBDC_MIN_TEMP_ALADINTEC 26 #define LIBDC_GASMIX_ALADINTEC 30 #define LIBDC_GASMIX_ALADINTEC2G 34 #define LIBDC_SETTINGS_ALADINTEC2G 60 #define LIBDC_SETTINGS_TRIMIX 68 #define LIBDC_SAMPLES_MANTIS 152 #define LIBDC_SAMPLES_G2 84 #define LIBDC_SAMPLES_ALADINTEC 108 #define LIBDC_SAMPLES_ALADINTEC2G 116 #define LIBDC_SAMPLES_SMART 92 #define LIBDC_SAMPLES_SMARTCOM 100 #define LIBDC_SAMPLES_SMARTZ 132 /* * Data positions in asd raw data buffer passed to build_dc_data(). * There are 12 more bytes which include 2bytes for dc model and 4bytes for * device serial number. */ #define ASD_SAMPLES 183 #define ASD_DIVE_TIME 32 #define ASD_MAX_TEMP 21 #define ASD_MIN_TEMP 34 #define ASD_SURF_TEMP 18 #define ASD_GAS_MIX 36 // 2 bytes. Should be x3 at least. Assume consecutive. #define ASD_MAXDEPTH 30 #define ASD_SETTINGS 70 // Get 4 bytes although only 2 are really needed #define ASD_TANK_PRESS_INIT 42 #define ASD_TANK_PRESS_END 44 #define G2_MAX_TEMP 148 /* * Returns a dc_descriptor_t structure based on dc model's number. * This ensures the model pased to libdc_buffer_parser() is a supported model and avoids * problems with shared model num devices by taking the family into account. * AFAIK only DC_FAMILY_UWATEC_SMART is avaliable in libdc; UWATEC_MERIDIAN kept just in case ... */ extern "C" dc_descriptor_t *get_data_descriptor(int data_model, dc_family_t data_fam) { dc_descriptor_t *descriptor = NULL, *current = NULL; dc_iterator_t *iterator = NULL; dc_status_t rc; rc = dc_descriptor_iterator(&iterator); if (rc != DC_STATUS_SUCCESS) { report_error("[libdc]\t\t\tCreating the device descriptor iterator.\n"); return current; } while ((dc_iterator_next(iterator, &descriptor)) == DC_STATUS_SUCCESS) { int desc_model = dc_descriptor_get_model(descriptor); dc_family_t desc_fam = dc_descriptor_get_type(descriptor); if (data_model == desc_model && (data_fam == desc_fam)) { current = descriptor; break; } dc_descriptor_free(descriptor); } dc_iterator_free(iterator); return current; } /* * Fills a device_data_t structure to pass to libdivecomputer. * Detects if a device is supported or not. */ extern "C" int prepare_data(int data_model, dc_family_t dc_fam, device_data_t *dev_data) { dev_data->device = NULL; dev_data->context = NULL; dev_data->descriptor = get_data_descriptor(data_model, dc_fam); if (dev_data->descriptor) { dev_data->vendor = dc_descriptor_get_vendor(dev_data->descriptor); dev_data->product = dc_descriptor_get_product(dev_data->descriptor); std::string tmp (dev_data->vendor); tmp += " "; tmp += dev_data->product; dev_data->model = tmp; return DC_STATUS_SUCCESS; } else { report_info("Warning [prepare_data]: Unsupported DC model 0x%2x\n", data_model); return DC_STATUS_UNSUPPORTED; } } /* * ASD provides the size of the buffer ... well, most times. Sometimes it's not the full buffer * size, but just the samples size. I've been unable to find a pattern for this (although samples * size seems to be limited to galileo devices coming from SmartTrak exported dives). Thus, we can't * trust this data and we need to calculate the correct buffer size, this being the previously found * size of the ASD dive data minus the ASD header plus the device header. * The default option in the switch conditional shouldn't be reached. If so, we have forgotten to add * some device. */ extern "C" unsigned char *allocate_libdc_buffer(int model, int asd_size, int *size) { unsigned char *buf; int sample_size = asd_size - ASD_SAMPLES; switch (model) { case GALILEO: case ALADIN2G: case MERIDIAN: case CHROMIS: case MANTIS2: case ALADINSQUARE: *size = sample_size + LIBDC_SAMPLES_MANTIS; break; case GALILEOTRIMIX: case G2: case G2HUD: case G2TEK: case G3: case ALADINSPORTMATRIX: case ALADINA1: case ALADINA2: case LUNA2AI: case LUNA2: *size = sample_size + LIBDC_SAMPLES_G2; break; case ALADINTEC2G: *size = sample_size + LIBDC_SAMPLES_ALADINTEC2G; break; case ALADINTEC: *size = sample_size + LIBDC_SAMPLES_ALADINTEC; break; case SMARTPRO: *size = sample_size + LIBDC_SAMPLES_SMART; break; case SMARTCOM: *size = sample_size + LIBDC_SAMPLES_SMARTCOM; break; default: report_error("[allocate_libdc_buffer]: Unsupported model 0x%.2X\n", model); return NULL; } buf = (unsigned char *)calloc(*size, 1); if (!buf) report_error("[allocate_libdc_buffer]: Failed to place enough memory for libdc buffer. %d bytes.\n", *size); return buf; } /* * This function returns an allocated memory buffer with the completed dc data. * The buffer has to be padded in the beginning with the header libdivecomputer expects to find, * this is, a5a55a5a following two bytes with the buffer size. * BTW we need to manually relocate those parts of the header needed by libdivecomputer. The * rest of the buffer is filled with the samples. * For older serial Aladin DCs, just use dt_libdc_buffer() from datatrak.cpp importer. */ extern "C" unsigned char *build_dc_data(int model, dc_family_t dc_fam, char *input, int max, int *out_size) { unsigned char *ptr = (unsigned char*) input; unsigned char *buffer; const unsigned char head_begin[] = {0xa5, 0xa5, 0x5a, 0x5a}; int buf_size = 0; // Older serial Aladin profile imported to // SmarTrak from Datatrak if (dc_fam == DC_FAMILY_UWATEC_ALADIN) { *out_size = max + 18; buffer = (unsigned char *)calloc(*out_size, 1); dt_libdc_buffer(ptr, max, model, buffer); return buffer; } buffer = allocate_libdc_buffer(model, max, &buf_size); if (!buffer) return NULL; // we are OOM or with unknown DC *out_size = buf_size; memcpy(buffer, &head_begin, 4); // place header begining buffer[4] = buf_size & 0xFF; // calculated buffer size buffer[5] = buf_size >> 8; memcpy(buffer + 6, ptr + 2, 11); // initial block (unchanged) switch (model) { case GALILEO: // untested with LogTrak native dives and somehow faulty case GALILEOTRIMIX: memcpy(buffer + LIBDC_MAX_DEPTH, ptr + ASD_MAXDEPTH, 2); memcpy(buffer + LIBDC_DIVE_TIME, ptr + ASD_DIVE_TIME, 2); memcpy(buffer + LIBDC_MAX_TEMP, ptr + ASD_MAX_TEMP, 2); memcpy(buffer + LIBDC_MIN_TEMP, ptr + ASD_MIN_TEMP, 2); memcpy(buffer + LIBDC_SURF_TEMP, ptr + ASD_SURF_TEMP, 2); memcpy(buffer + LIBDC_GASMIX, ptr + ASD_GAS_MIX, 2); memcpy(buffer + LIBDC_TANK_PRESS, ptr + ASD_TANK_PRESS_INIT, 2); if (model == GALILEOTRIMIX) buffer[43] = 0x80; // libdc checks this byte to apply trimix's parsing model or galileo's (model == GALILEO) ? memcpy(buffer + LIBDC_SETTINGS, ptr + ASD_SETTINGS, 4) : memcpy(buffer + LIBDC_SETTINGS_TRIMIX, ptr + ASD_SETTINGS, 4); (model == GALILEO) ? memcpy(buffer + LIBDC_SAMPLES_MANTIS, ptr + ASD_SAMPLES, max - ASD_SAMPLES) : memcpy(buffer + LIBDC_SAMPLES_G2, ptr + ASD_SAMPLES, max - ASD_SAMPLES); break; case ALADINTEC2G: memcpy(buffer + LIBDC_MAX_DEPTH_SMART, ptr + ASD_MAXDEPTH, 2); memcpy(buffer + LIBDC_DIVE_TIME_SMART, ptr + ASD_DIVE_TIME, 2); memcpy(buffer + LIBDC_MAX_TEMP, ptr + ASD_MAX_TEMP, 2); memcpy(buffer + LIBDC_MIN_TEMP_ALADINTEC, ptr + ASD_MIN_TEMP, 2); memcpy(buffer + LIBDC_SURF_TEMP, ptr + ASD_SURF_TEMP, 2); memcpy(buffer + LIBDC_GASMIX_ALADINTEC2G, ptr + ASD_GAS_MIX, 6); memcpy(buffer + LIBDC_SETTINGS_ALADINTEC2G, ptr + ASD_SETTINGS, 4); memcpy(buffer + LIBDC_SAMPLES_ALADINTEC2G, ptr + ASD_SAMPLES, max - ASD_SAMPLES); break; case ALADIN2G: // tested Logtrak native Aladin 2G and Mantis 2 case MERIDIAN: case CHROMIS: case MANTIS2: case ALADINSQUARE: memcpy(buffer + LIBDC_MAX_DEPTH, ptr + ASD_MAXDEPTH, 2); memcpy(buffer + LIBDC_DIVE_TIME, ptr + ASD_DIVE_TIME, 2); memcpy(buffer + LIBDC_MAX_TEMP, ptr + ASD_MAX_TEMP, 2); memcpy(buffer + LIBDC_MIN_TEMP, ptr + ASD_MIN_TEMP, 2); memcpy(buffer + LIBDC_SURF_TEMP, ptr + ASD_SURF_TEMP, 2); memcpy(buffer + LIBDC_GASMIX, ptr + ASD_GAS_MIX, 2); memcpy(buffer + LIBDC_TANK_PRESS, ptr + ASD_TANK_PRESS_INIT, 2); memcpy(buffer + LIBDC_SETTINGS, ptr + ASD_SETTINGS, 4); memcpy(buffer + LIBDC_SAMPLES_MANTIS, ptr + ASD_SAMPLES, max - ASD_SAMPLES); break; case G2: // only G2 tested, bold assumption case G2HUD: case G2TEK: case G3: case ALADINSPORTMATRIX: case ALADINA1: case ALADINA2: case LUNA2AI: case LUNA2: memcpy(buffer + LIBDC_MAX_DEPTH, ptr + ASD_MAXDEPTH, 2); memcpy(buffer + LIBDC_DIVE_TIME, ptr + ASD_DIVE_TIME, 2); memcpy(buffer + LIBDC_MAX_TEMP, ptr + G2_MAX_TEMP, 2); memcpy(buffer + LIBDC_MIN_TEMP, ptr + ASD_MIN_TEMP, 2); memcpy(buffer + LIBDC_SURF_TEMP, ptr + ASD_SURF_TEMP, 2); memcpy(buffer + LIBDC_SETTINGS_TRIMIX, ptr + ASD_SETTINGS, 4); memcpy(buffer + LIBDC_SAMPLES_G2, ptr + ASD_SAMPLES, max - ASD_SAMPLES); break; case ALADINTEC: memcpy(buffer + LIBDC_MAX_DEPTH, ptr + ASD_MAXDEPTH, 2); memcpy(buffer + LIBDC_DIVE_TIME_ALADINTEC, ptr + ASD_DIVE_TIME, 2); memcpy(buffer + LIBDC_MAX_TEMP, ptr + ASD_SURF_TEMP, 2); memcpy(buffer + LIBDC_MIN_TEMP_ALADINTEC, ptr + ASD_MIN_TEMP, 2); memcpy(buffer + LIBDC_GASMIX_ALADINTEC, ptr + ASD_GAS_MIX, 2); memcpy(buffer + LIBDC_SETTINGS_ALADINTEC, ptr + ASD_SETTINGS, 4); memcpy(buffer + LIBDC_SAMPLES_ALADINTEC, ptr + ASD_SAMPLES, max - ASD_SAMPLES); break; case SMARTPRO: memcpy(buffer + LIBDC_MAX_DEPTH_SMART, ptr + ASD_MAXDEPTH, 2); memcpy(buffer + LIBDC_DIVE_TIME_SMART, ptr + ASD_DIVE_TIME, 2); memcpy(buffer + LIBDC_MIN_TEMP_SMART, ptr + ASD_MIN_TEMP, 2); memcpy(buffer + LIBDC_GASMIX_SMART, ptr + ASD_GAS_MIX, 2); memcpy(buffer + LIBDC_SAMPLES_SMART, ptr + ASD_SAMPLES, max - ASD_SAMPLES); break; case SMARTCOM: memcpy(buffer + LIBDC_MAX_DEPTH_SMART, ptr + ASD_MAXDEPTH, 2); memcpy(buffer + LIBDC_DIVE_TIME_SMART, ptr + ASD_DIVE_TIME, 2); memcpy(buffer + LIBDC_MIN_TEMP_SMART, ptr + ASD_MIN_TEMP, 2); memcpy(buffer + LIBDC_GASMIX_SMART, ptr + ASD_GAS_MIX, 2); memcpy(buffer + LIBDC_TANK_PRESS_SMARTCOM, ptr + ASD_TANK_PRESS_INIT, 4); memcpy(buffer + LIBDC_SAMPLES_SMARTCOM, ptr + ASD_SAMPLES, max - ASD_SAMPLES); break; default: report_error("Unsupported DC model 0x%2x\n", model); free(buffer); buffer = NULL; } return buffer; } /* * Search for a given sequence of bytes in a string. * Returns the position of the first byte of the sequence or string::npos if * not found. * Parameter s is the size of the sequence. */ std::size_t uchar_find(const std::string &in, const unsigned char *seq, const int s) { return in.find(std::string(reinterpret_cast(seq), s)); } /* * Return a utf-8 string from an .asd string. * String fields in .asd file begin with a 3 bytes BOM followed by a 1 byte * number, e.g. FF FE FF nn, where nn is the length of string following, * in chars (two bytes unicode chars). May be zero, meaning an empty field. * If anything fails the pointer to next field will be set to "", signaling * a weirdness. The caller must handle it (aborting seems preferable as we can * have data corruption issues). */ std::string asd_to_string(const std::string &input, std::string &output) { if (input.empty()) { output = input; return input; } const auto tmp = reinterpret_cast(input.data()) ; const int size = tmp[3] * 2; // worst case, all chars are 2 bytes long // this is not a failure but an empty string if (size == 0) { output = input.substr(4); return ""; } // convert string to UTF-8 std::vector buffer(size, 0); for (int i = 0, j = 0; i < size; i += 2, ++j) { const unsigned char c = (tmp[i + 5] << 4) + tmp[i + 4]; // 4 is the BOM size if (c <= 0x7F) { buffer[j] = c; } else { buffer[j] = (c >> 6) | 0xC0; buffer[++j] = (c & 0x3F) | 0x80; } } output = input.substr(size + 4); buffer.erase(std::remove(buffer.begin(), buffer.end(), 0), buffer.end()); const std::string ret = std::string(buffer.begin(), buffer.end()); // drop meaningless strings comming from SmartTrak if any return ret.compare(" ") && ret.compare("-") && ret.compare("???") && ret.compare("---") ? ret : ""; } /* * Build a dive site with coords and name if it doesn't exist yet and place it in the table. */ static void asd_build_dive_site(const std::string &instring, const std::string &coords, struct divelog *log, struct dive_site **asd_site) { struct dive_site *site; double gpsX = 0, gpsY = 0; location_t gps_loc; if (!coords.empty()) sscanf(coords.data(), "%lf %lf", &gpsX, &gpsY); gps_loc = create_location(gpsX, gpsY); site = log->sites.get_by_name(instring); if (!site) { if (!has_location(&gps_loc)) site = log->sites.create(instring); else site = log->sites.create(instring, gps_loc); } *asd_site = site; } /* * Check if file is an export from SmartTrak divelog. * ASD files have a unicode string at the beginning with some info, including * the string "SmartTRAK" if generated with this software, */ bool is_smtk(const std::string &p) { const unsigned char smart[10] = {0x53,0x00,0x6d,0x00,0x61,0x00,0x72,0x00,0x74,0x00}; return uchar_find(p, smart, 10) < p.find("CLogEntry"); } /* * A block of buddies or equipment consist of a integer n (2 bytes big endian) followed by * n asd strings, n = 0 meaning no strings follow. Caller func passes n (if n > 0) and points * to the beginning of strings. Buddies are placed in dive buddies list and equipment in a string, * returning a pointer to next byte after the parsed block. * These are only used in SmartTrak exported .asd files, as LogTrak doesn't support such fields. */ std::string asd_build_others(const std::string &input, int idx, std::string &output) { std::string head; std::string ptr = input; int i = idx; if (input.empty()) { output = ""; return ""; } while (i > 0) { std::string tail = asd_to_string(ptr, ptr); if (! head.empty()) head += ", "; head += tail; i--; } output = head; return ptr; } /* * Parse a dive in a mem buffer and return 0 for correct ending or negative * values signaling an error. -1 would be a recoverable error and -2 * a weird issue (abort further parsing would be preferred). */ static int asd_dive_parser(const std::string &input, struct dive *asd_dive, struct divelog *log)//, const device_table &devices) { int dc_model, s = 0, rc = 0, k, j; dc_family_t dc_fam; long dc_serial; const unsigned char str_seq[] = {0xff, 0xfe, 0xff}, dc_profile_begin[4] = {0x01, 0x00, 0x00, 0xFF}; unsigned char *dc_data; auto devdata = std::make_unique(); devdata->log = log; asd_dive->dcs[0].serial.resize(64); weightsystem_t ws; std::string tmp, d_locat, d_point, d_coords, notes, viz, w_type, w_surf, weather, buddies, equipment; std::size_t size; //input should point to dc model integer dc_model = input[0]; dc_fam = (input[1] > 0x00) ? DC_FAMILY_UWATEC_ALADIN : DC_FAMILY_UWATEC_SMART; tmp = input.substr(8); dc_serial = (tmp[3] << 24) + (tmp[2] << 16) + (tmp[1] << 8) + tmp[0]; if (!dc_model) { // this would be the manual dive case (coming from SmartTrak) asd_dive->dcs[0].model = "Manually entered dive"; } else { rc = prepare_data(dc_model, dc_fam, devdata.get()); if (rc != DC_STATUS_SUCCESS) goto bailout; asd_dive->dcs[0].model = devdata->model; tmp = tmp.substr(4); if (dc_fam == DC_FAMILY_UWATEC_ALADIN) { std::size_t pos = uchar_find(tmp, dc_profile_begin, 4); tmp = tmp.substr(pos + 4); } size = uchar_find(tmp, str_seq, 3); // size of DC data dc_data = build_dc_data(dc_model, dc_fam, tmp.data(), size, &s); if (!dc_data) goto bailout; rc = libdc_buffer_parser(asd_dive, devdata.get(), dc_data, s); free(dc_data); if (rc != DC_STATUS_SUCCESS) goto bailout; // set serial in DC info, and set a node for the device. asd_dive->dcs[0].serial = std::to_string(dc_serial); create_device_node(log->devices, asd_dive->dcs[0].model, asd_dive->dcs[0].serial, asd_dive->dcs[0].model); // Now the non DC data fields. tmp = tmp.substr(size); } // There are no string fields merged with string ones, making things a bit more dificult. d_locat = asd_to_string(tmp, tmp); if (tmp.empty()) goto buffail; d_point = asd_to_string(tmp, tmp); if (tmp.empty()) goto buffail; if (!d_point.empty() && d_locat.compare(d_point)) d_locat += ", " + d_point; d_coords = asd_to_string(tmp, tmp); if (tmp.empty()) goto buffail; asd_build_dive_site(d_locat, d_coords, log, &asd_dive->dive_site); // next two bytes are the tank volume (mililiters) and two following are // unknown (always zero, may be a 2º tank). asd_dive->get_or_create_cylinder(0)->type.size.mliter = (tmp[1] << 8) + tmp[0]; tmp = tmp.substr(4); asd_dive->get_cylinder(0)->type.description = asd_to_string(tmp, tmp); if (tmp.empty()) goto buffail; asd_dive->suit = asd_to_string(tmp, tmp); if (tmp.empty()) goto buffail; // next two bytes are the weight in gr. Two following are zeroed. ws = { { .grams = (tmp[1] << 8) + tmp[0] }, translate("gettextFromC", "unknown"), false }; asd_dive->weightsystems.push_back(std::move(ws)); tmp = tmp.substr(4); // weather weather = asd_to_string(tmp, tmp); if (!weather.empty()) taglist_add_tag(asd_dive->tags, weather); if (tmp.empty()) goto buffail; // water surface w_surf = asd_to_string(tmp,tmp); if (!w_surf.empty()) taglist_add_tag(asd_dive->tags, w_surf); if (tmp.empty()) goto buffail; // water type (localized string; can't be used for salinity) w_type = asd_to_string(tmp, tmp); if (!w_type.empty()) taglist_add_tag(asd_dive->tags, w_type); if (tmp.empty()) goto buffail; // visibility (localized string; can't be used for rating viz) viz = asd_to_string(tmp, tmp); if (!viz.empty()) taglist_add_tag(asd_dive->tags, viz); if (tmp.empty()) goto buffail; tmp = tmp.substr(2); // Nº of buddies strings following j = (tmp[1] << 8 ) + tmp[0]; tmp = tmp.substr(2); if (j > 0) { tmp = asd_build_others(tmp, j, buddies); asd_dive->buddy = buddies; } if (tmp.empty()) goto buffail; // Nº of equipment items strings following k = (tmp[1] << 8 ) + tmp[0]; tmp = tmp.substr(2); if (k > 0) tmp = asd_build_others(tmp, k, equipment); if (tmp.empty()) goto buffail; // Notes if any. Include equipment in notes. notes = asd_to_string(tmp, tmp); if (! equipment.empty()) { if (notes.size() > 0) { notes += "\n"; } notes += (translate("gettextFromC", "Equipment: ") + equipment); } asd_dive->notes = notes; if (tmp.empty()) goto buffail; return 0; bailout: // give a pointer, we can parse other dives yet report_error("Warning [asd_dive_parser]: Non critical error in dive %d. Keep on parsing next dive.\n", asd_dive->number); return -1; buffail: // quit caller func, something weird is going on report_error("[asd_dive_parser()]\tMemory corruption or file damaged. Dive %d\n", asd_dive->number); return -2; } /* * Main function */ int scubapro_asd_import(const std::string &mem, struct divelog *log) { std::string runner; const char init_seq[4] = {0x07, 0x00, 0x10, 0x00}; std::string first_dive_seq = {"CLogEntry"}; unsigned char dive_seq_lt[4] = {0x00, 0x00, 0x80, 0x01}; unsigned char dive_seq_smtk[4] = {0x00, 0x00, 0x01, 0x80}; const unsigned char *dive_seq = dive_seq_lt; int dive_count = 0; setlocale(LC_NUMERIC, "POSIX"); setlocale(LC_CTYPE, ""); // check header if (! mem.compare(0, 4, init_seq)) { report_error("This doesn't look like an .asd file. Please check it.\n"); return 1; } // is this .asd coming from SmartTrak ? if (is_smtk(mem)) dive_seq = dive_seq_smtk; // Jump to initial dive data secuence std::size_t pos = mem.find(first_dive_seq); if (pos != std::string::npos) { runner = mem.substr(pos + 9); } else { report_error("This doesn't look like an .asd file. Please check it.\n"); return 1; } // We are on the first byte of the first dive (DC model). Subsequent dives in log // will begin with 0x80 0x01 or 0x01 0x80 depending on original software. while (pos < std::string::npos) { auto asd_dive = std::make_unique(); dive_count++; asd_dive->number = dive_count; pos = 0; int rc = asd_dive_parser(runner, asd_dive.get(), log); switch (rc) { case 0: { log->dives.record_dive(std::move(asd_dive)); break; } case -1: { // recoverable error report_error("Error parsing dive %d\n", dive_count); break; } case -2: { // no recoverable error report_error("Error parsing dive %d\n", dive_count); return 1; } } // Look for next dive and jump to it pos = uchar_find(runner, dive_seq, 4); runner = runner.substr(pos + 4); }; log->dives.sort(); return 0; }