/*-------------------------------------------------------------------- * Modified for MiSTer by alanswx 2022 * * Originally from Replay Firmware */ /*-------------------------------------------------------------------- * Replay Firmware * www.fpgaarcade.com * All rights reserved. * * admin@fpgaarcade.com * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. *-------------------------------------------------------------------- * * Copyright (c) 2020, The FPGAArcade community (see AUTHORS.txt) * */ #include #include #include #include #include #include #include #include #include "../../file_io.h" #include "../../user_io.h" #include "../../menu.h" #include "zlib.h" #define UEF_ChunkHeaderSize (sizeof(uint16_t) + sizeof(uint32_t)) #define UEF_infoID 0x0000 #define UEF_tapeID 0x0100 #define UEF_highToneID 0x0110 #define UEF_highDummyID 0x0111 #define UEF_gapID 0x0112 #define UEF_freqChgID 0x0113 #define UEF_securityID 0x0114 #define UEF_floatGapID 0x0116 #define UEF_startBit 0 #define UEF_stopBit 1 #define UEF_Baud (1000000.0/(16.0*52.0)) typedef struct { // UEF header uint16_t id; uint32_t length; // chunk data uint32_t file_offset; uint32_t bit_offset_start; uint32_t bit_offset_end; uint32_t pre_carrier; } __attribute__((packed)) ChunkInfo; // TODO - allocate this as part of the desc structure static ChunkInfo s_ChunkData = { 0,0,0,0,0,0 }; static uint16_t ReadChunkHeader(FILE* f, ChunkInfo* chunk) { chunk->id = -1; chunk->length = 0; if (fread(chunk, 1, UEF_ChunkHeaderSize, f) != UEF_ChunkHeaderSize) { return (-1); } //fprintf(stderr, "ReadChunkHeader: chunk->id %d chunk->length %d\n", chunk->id,chunk->length); return chunk->id; } static ChunkInfo* GetChunkAtPosFile(FILE *f, uint32_t* p_bit_pos) { uint32_t bit_pos = *p_bit_pos; uint8_t length_check = (bit_pos == 0xffffffff); ChunkInfo* chunk = &s_ChunkData; //fprintf(stderr, "Find pos : %d\n", bit_pos); if (chunk->bit_offset_start <= bit_pos && bit_pos < chunk->bit_offset_end) { bit_pos -= chunk->bit_offset_start; *p_bit_pos = bit_pos; return chunk; } uint32_t chunk_start = 0; uint32_t chunk_bitlen = 0; if (chunk->bit_offset_end != 0 && bit_pos >= chunk->bit_offset_end) { fseek(f, chunk->file_offset + chunk->length, SEEK_SET); chunk_start = chunk->bit_offset_end; bit_pos -= chunk_start; } else { fseek(f, 12, SEEK_SET); // sizeof(UEF_header) } chunk->bit_offset_end = 0; while (!feof(f)) { uint16_t id = ReadChunkHeader(f, chunk); if (id == (uint16_t) - 1) { break; } //fprintf(stderr, "Parse ChunkID : %04x - Length : %4d bytes (%04x) - Offset = %d\n", chunk->id, chunk->length, chunk->length, (uint32_t)ftell(f)); chunk->file_offset = ftell(f); if (UEF_tapeID == id || UEF_gapID == id || UEF_highToneID == id || UEF_highDummyID == id) { if (id == UEF_tapeID) { chunk_bitlen = chunk->length * 10; } else if (id == UEF_gapID || id == UEF_highToneID) { uint16_t ms; if (fread(&ms, 1, sizeof(ms), f) != sizeof(ms)) { break; } chunk_bitlen = ms * (UEF_Baud / 1000.0); fseek(f, -sizeof(ms), SEEK_CUR); } else if (id == UEF_highDummyID) { uint16_t ms; if (fread(&ms, 1, sizeof(ms), f) != sizeof(ms)) { break; } chunk->pre_carrier = ms * (UEF_Baud / 1000.0); if (fread(&ms, 1, sizeof(ms), f) != sizeof(ms)) { break; } uint32_t post_carrier = ms * (UEF_Baud / 1000.0); chunk_bitlen = chunk->pre_carrier + 20 + post_carrier; fseek(f, -sizeof(ms) * 2, SEEK_CUR); } if (bit_pos < chunk_bitlen) { chunk->bit_offset_start = chunk_start; chunk->bit_offset_end = chunk_start + chunk_bitlen; break; } bit_pos -= chunk_bitlen; chunk_start += chunk_bitlen; } else if (length_check) { if (UEF_infoID == id) { char buffer[64]; uint32_t length = chunk->length; while (length > 0) { uint32_t read_len = length; if (read_len > sizeof(buffer) - 1) { read_len = sizeof(buffer) - 1; } if (fread(buffer, 1, read_len, f) != read_len) { break; } buffer[read_len] = '\0'; fprintf(stderr, "Drv02:UEF Info : '%s'", buffer); length -= read_len; } } else if (UEF_freqChgID == id) { float freq; if (fread(&freq, 1, sizeof(freq), f) != sizeof(freq)) { break; } fprintf(stderr, "Drv02:Ignoring base frequency change : %d", (int)freq); } else if (UEF_floatGapID == id) { float gap; if (fread(&gap, 1, sizeof(gap), f) != sizeof(gap)) { break; } fprintf(stderr, "Drv02:Ignoring floating point gap : %d ms", (int)(gap * 1000.f)); } else if (UEF_securityID == id) { fprintf(stderr, "Drv02:UEF security block ignored"); } else { fprintf(stderr, "Drv02:Unknown UEF block ID %04x", id); } fseek(f, chunk->file_offset, SEEK_SET); } fseek(f, chunk->length, SEEK_CUR); } /*DEBUG(1, "OK!");*/ *p_bit_pos = bit_pos; return chunk->bit_offset_end ? chunk : 0; } static uint8_t GetBitAtPos(FILE *f, uint32_t bit_pos) { ChunkInfo* info = GetChunkAtPosFile(f, &bit_pos); if (!info) { return 0; } uint16_t id = info->id; if (id == UEF_gapID) { return 0; } else if (id == UEF_highToneID) { return 1; } if (id == UEF_tapeID) { uint32_t byte_offset = bit_pos / 10; uint32_t bit_offset = bit_pos - byte_offset * 10; if (bit_offset == 0) { return UEF_startBit; } if (bit_offset == 9) { return UEF_stopBit; } uint8_t byte; fseek(f, info->file_offset + byte_offset, SEEK_SET); int ret=fread(&byte, 1, sizeof(byte), f); if (ret<0) fprintf(stderr,"uef: error reading byte\n"); bit_offset -= 1; // E (0,7) assert(bit_offset < 8); return (byte & (1 << bit_offset)) ? 1 : 0; } assert(id == UEF_highDummyID); if ((bit_pos < info->pre_carrier) || (bit_pos >= info->pre_carrier + 20)) { return 1; } bit_pos -= info->pre_carrier; bit_pos %= 10; if (bit_pos == 0) { return UEF_startBit; } if (bit_pos == 9) { return UEF_stopBit; } bit_pos -= 1; // E (0,7) assert(bit_pos < 8); uint8_t byte = 'A'; return (byte & (1 << bit_pos)) ? 1 : 0; } #define BUFLEN 16384 #define CHUNK 16384 #define kBufferSize 4096 static int uef_copy_file(fileTYPE *source, FILE *dest) { unsigned char in[CHUNK]; int num_bytes; do { num_bytes = FileReadAdv(source, in, CHUNK,-1); fprintf(stderr,"fread: %d\n",num_bytes); if (num_bytes<0) { fprintf(stderr,"uef_copy_file: error reading data\n"); return -1; } if (fwrite(in, 1, num_bytes, dest) != (size_t)num_bytes || ferror(dest)) { fprintf(stderr,"uef_copy_file: error writing data\n"); return -1; } } while (num_bytes!=0); return 0; } /* Decompress from file source to file dest until stream ends or EOF. inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be allocated for processing, Z_DATA_ERROR if the deflate data is invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and the version of the library linked do not match, or Z_ERRNO if there is an error reading or writing the files. */ static int uef_inflate_file(fileTYPE *source, FILE *dest) { int ret; unsigned have; z_stream strm; unsigned char in[CHUNK]; unsigned char out[CHUNK]; /* allocate inflate state */ strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = 0; strm.next_in = Z_NULL; ret = inflateInit2(&strm,MAX_WBITS|16); // make sure to add the 16 to get it to accept gz header if (ret != Z_OK) return ret; /* decompress until deflate stream ends or end of file */ do { int res = FileReadAdv(source, in, CHUNK,-1); strm.avail_in= res; if (res<0) { (void)inflateEnd(&strm); return Z_ERRNO; } if (strm.avail_in == 0) break; strm.next_in = in; /* run inflate() on input until output buffer not full */ do { strm.avail_out = CHUNK; strm.next_out = out; ret = inflate(&strm, Z_NO_FLUSH); assert(ret != Z_STREAM_ERROR); /* state not clobbered */ switch (ret) { case Z_NEED_DICT: ret = Z_DATA_ERROR; /* and fall through */ (void)inflateEnd(&strm); return ret; break; case Z_DATA_ERROR: (void)inflateEnd(&strm); return ret; break; case Z_MEM_ERROR: (void)inflateEnd(&strm); return ret; break; } have = CHUNK - strm.avail_out; if (fwrite(out, 1, have, dest) != have || ferror(dest)) { (void)inflateEnd(&strm); return Z_ERRNO; } } while (strm.avail_out == 0); /* done when inflate() says it's done */ } while (ret != Z_STREAM_END); /* clean up and return */ (void)inflateEnd(&strm); return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; } int UEF_FileSend(fileTYPE *inputfile,int use_progress) { uint32_t buf_size=kBufferSize; unsigned char fbuf[kBufferSize]; int addr=0; typedef struct { char ueftag[10]; uint8_t minor_version; uint8_t major_version; } UEF_header; UEF_header header; // the UAE file might be gzipped, if so we need to ungzip it // gzip : 1f 8b if ( FileReadAdv(inputfile, &fbuf,2) !=2) { fprintf(stderr,"error reading 2 bytes of file\n"); return 0; } // we need to rewind to the beginning FileSeek(inputfile, 0, SEEK_SET); FILE *uncompressed_file= tmpfile(); // 1f 8b is the gzip magic number if (fbuf[0]==0x1f && fbuf[1]==0x8b) { fprintf(stderr,"UEF is compressed\n"); uef_inflate_file(inputfile, uncompressed_file); } else { uef_copy_file(inputfile,uncompressed_file); fprintf(stderr,"UEF is not compressed\n"); } rewind(uncompressed_file); if (fread(&header, 1, sizeof(UEF_header), uncompressed_file) != sizeof(UEF_header)) { fprintf(stderr,"Couldn't read file header\n"); } else if (memcmp(header.ueftag, "UEF File!\0", sizeof(header.ueftag)) != 0) { fprintf(stderr,"UEF file header mismatch\n"); fprintf(stderr,"File compressed?\n"); } else { fprintf(stderr,"UEF: %s %d %d\n",header.ueftag,header.minor_version,header.major_version); // figure out how big the file is fseek(uncompressed_file, 0L, SEEK_END); uint32_t size =ftell(uncompressed_file); fprintf(stderr,"size: %d\n",size); rewind(uncompressed_file); //FILE *outfile = fopen("tape.raw","wb"); // // Read the header to find out how big the audio file should be // memset(&s_ChunkData, 0x00, sizeof(ChunkInfo)); uint32_t numbits = 0xffffffff; GetChunkAtPosFile(uncompressed_file,&numbits); numbits = ~numbits; uint32_t bits_per_second = 1225; fprintf(stderr, "Bit length : %d\n", numbits); fprintf(stderr, "Wave length : %ds\n", numbits / bits_per_second); fprintf(stderr, "Byte length : %d\n", (numbits + 7) / 8); // size is the output size of the file we are creating (or dynamically sending) size= (numbits + 7) / 8; uint32_t orig_size=size; fprintf(stderr,"output size: %d\n",size); // clear the header memset(&s_ChunkData, 0x00, sizeof(ChunkInfo)); uint32_t tot_size=0; uint32_t cur_size=0; uint32_t act_size=0; while (size) { cur_size= size; if (cur_size> buf_size) cur_size=buf_size; act_size=cur_size; // artifically clamp size to the end of the bit stream if (addr + act_size > orig_size) act_size = orig_size - addr; tot_size+=act_size; // this is a very naive conversion, but it'll have to do for now.. for (uint32_t pos = 0; pos < act_size; ++pos) { uint8_t val = 0; for (uint32_t bit = 0; bit < 8; ++bit) { val = val << 1; val = val | GetBitAtPos(uncompressed_file, ((addr + pos) << 3) + bit); } fbuf[pos] = val; } if (use_progress) ProgressMessage("Loading", inputfile->name, orig_size-size , orig_size); user_io_file_tx_data(fbuf, act_size); if (act_size!=cur_size) fprintf(stderr,"truncated?\n"); //fwrite(fbuf,1,act_size,outfile); size -= cur_size; addr += cur_size; } fclose(uncompressed_file); //fclose(outfile); } return 0; }