diff --git a/README.md b/README.md index 47c7114..f36af8a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ Random single-file programs I've written in my spare time for small tasks. +* bleep: I got bored and decided to write a pc speaker music program. * ckmextract: Extracts ESP and BSA from Skyrim steam workshop mod archives. * ddsinfo: Shows contents of a DDS header. * dood: Reads an ENDOOM lump and mirrors every word "down the middle". @@ -19,6 +20,7 @@ Random single-file programs I've written in my spare time for small tasks. * mkwall: A program I use on a daily basis to set my wallpaper on every Linux machine. * osnorm: Experiment for generating object-space normals from an .obj model. +* pframes: Short utility for automating long FrameIndex lists for MODELDEF. * schange: Program used along with mkwall to update the wallpaper on screen geometry changes. * skse_cosave: Experiment for dumping information in SKSE co-saves. @@ -26,6 +28,8 @@ Random single-file programs I've written in my spare time for small tasks. can be generated in bulk. * totty: Sends text from stdin to tty1. Used to send certain commands when remoting into a Raspberry Pi. +* umxunpack: Extractor for music in UE archives, with support for Unreal 227's + UMX files containing vorbis audio. * vc2sdl: Passes the contents of the VC4 framebuffer to a SDL window. Was used for video playback experiments on a Raspberry Pi with a SPI LCD. * withhands: Talk like W.D. Gaster. diff --git a/bleep.c b/bleep.c new file mode 100644 index 0000000..081914f --- /dev/null +++ b/bleep.c @@ -0,0 +1,84 @@ +/* needed for usleep(), feature test macros are annoying */ +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int fd; + +/* beep boop */ +void beep( int freq ) +{ + struct input_event e = + { + .type = EV_SND, + .code = SND_TONE, + .value = freq, + }; + write(fd,&e,sizeof(struct input_event)); +} + +void sighnd( int signum ) +{ + switch ( signum ) + { + case SIGINT: + case SIGTERM: + case SIGQUIT: + if ( fd >= 0 ) + { + /* better clean up or this is going to beep forever */ + beep(0); + close(fd); + } + exit(signum); + } +} + +int main( int argc, char **argv ) +{ + signal(SIGINT,sighnd); + signal(SIGTERM,sighnd); + signal(SIGQUIT,sighnd); + if ( argc < 2 ) + return fprintf(stderr,"usage: %s \n",argv[0])||1; + fd = open(argv[1],O_RDWR); + if ( fd < 0 ) + return fprintf(stderr,"failed to open %s: %s\n",argv[1], + strerror(errno))||1; + uint8_t ver[4] = {0}; + if ( ioctl(fd,EVIOCGVERSION,ver) == -1 ) + return fprintf(stderr,"ioctl failed: %s\n",strerror(errno))||1; + printf("Driver ver: %hhu.%hhu.%hhu\n",ver[2],ver[1],ver[0]); + struct input_id id = {0}; + ioctl(fd,EVIOCGID,&id); + printf("ID: bus %#06hx vendor %#06hx product %#06hx ver %#06hx\n", + id.bustype,id.vendor,id.product,id.version); + char name[256] = "Unknown device"; + ioctl(fd,EVIOCGNAME(256),name); + printf("Name: %s\n",name); + uint64_t evbits, sndbits; + if ( ioctl(fd,EVIOCGBIT(0,8),&evbits) == -1 ) + return fprintf(stderr,"ioctl failed: %s\n",strerror(errno))||1; + if ( !((evbits>>EV_SND)&1) ) + return fprintf(stderr,"No sound support\n")||1; + if ( ioctl(fd,EVIOCGBIT(EV_SND,8),&sndbits) == -1 ) + return fprintf(stderr,"ioctl failed: %s\n",strerror(errno))||1; + if ( !((sndbits>>SND_TONE)&1) ) + return fprintf(stderr,"No tone event support\n")||1; + int freq = 200, time = 200; + printf("Testing %dHz tone for %dms\n",freq,time); + beep(200); + usleep(time*1000UL); + beep(0); + close(fd); + return 0; +} diff --git a/pframes.c b/pframes.c new file mode 100644 index 0000000..d40b2cb --- /dev/null +++ b/pframes.c @@ -0,0 +1,32 @@ +#include +#include + +int main( int argc, char **argv ) +{ + if ( argc < 5 ) return 1; + char s[4] = "TNT1"; + char f = 'A'; + int fi = 0; + int sp = 1; + int max = 0; + int sk = 1; + strncpy(s,argv[1],4); + sscanf(argv[2],"%d",&fi); + sscanf(argv[3],"%d",&max); + sscanf(argv[4],"%d",&sk); + for ( int i=0; i 'Z' ) + { + sp++; + if ( sp == 2 ) + s[2] = s[3]; + s[3] = '0'+sp; + f = 'A'; + } + } + return 0; +} diff --git a/umxunpack.c b/umxunpack.c new file mode 100644 index 0000000..2d492ce --- /dev/null +++ b/umxunpack.c @@ -0,0 +1,313 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UPKG_MAGIC 0x9E2A83C1 + +typedef struct +{ + uint32_t magic; + uint16_t pkgver, license; + uint32_t flags, nnames, onames, nexports, oexports, nimports, oimports; +} upkg_header_t; + +uint8_t *pkgfile; +upkg_header_t *head; +size_t fpos = 0; + +uint8_t readbyte( void ) +{ + uint8_t val = pkgfile[fpos]; + fpos++; + return val; +} + +uint32_t readdword( void ) +{ + uint32_t val = *(uint32_t*)(pkgfile+fpos); + fpos += 4; + return val; +} + +// reads a compact index value +int32_t readindex( void ) +{ + uint8_t byte[5] = {0}; + byte[0] = readbyte(); + if ( !byte[0] ) return 0; + if ( byte[0]&0x40 ) + { + for ( int i=1; i<5; i++ ) + { + byte[i] = readbyte(); + if ( !(byte[i]&0x80) ) break; + } + } + int32_t tf = byte[0]&0x3f; + tf |= (int32_t)(byte[1]&0x7f)<<6; + tf |= (int32_t)(byte[2]&0x7f)<<13; + tf |= (int32_t)(byte[3]&0x7f)<<20; + tf |= (int32_t)(byte[4]&0x7f)<<27; + if ( byte[0]&0x80 ) tf *= -1; + return tf; +} + +// reads a name table entry +size_t readname( int *olen ) +{ + size_t pos = fpos; + if ( head->pkgver >= 64 ) + { + int32_t len = readindex(); + pos = fpos; + if ( olen ) *olen = len; + if ( len <= 0 ) return pos; + fpos += len; + } + else + { + int c, p = 0; + while ( c = readbyte() ) p++; + if ( olen ) *olen = p; + } + fpos += 4; + return pos; +} + +size_t getname( int index, int *olen ) +{ + size_t prev = fpos; + fpos = head->onames; + size_t npos = 0; + for ( int i=0; i<=index; i++ ) + npos = readname(olen); + fpos = prev; + return npos; +} + +// checks if a name exists +int hasname( const char *needle ) +{ + if ( !needle ) return 0; + size_t prev = fpos; + fpos = head->onames; + int found = 0; + int nlen = strlen(needle); + for ( int i=0; innames; i++ ) + { + int32_t len = 0; + if ( head->pkgver >= 64 ) + { + len = readindex(); + if ( len <= 0 ) continue; + } + int c = 0, p = 0, match = 1; + while ( c = readbyte() ) + { + if ( (p >= nlen) || (needle[p] != c) ) match = 0; + p++; + if ( len && (p > len) ) break; + } + if ( match ) + { + found = 1; + break; + } + fpos += 4; + } + fpos = prev; + return found; +} + +// read import table entry and return index of its object name +int32_t readimport( void ) +{ + readindex(); + readindex(); + if ( head->pkgver >= 60 ) fpos += 4; + else readindex(); + return readindex(); +} + +int32_t getimport( int index ) +{ + size_t prev = fpos; + fpos = head->oimports; + int32_t iname = 0; + for ( int i=0; i<=index; i++ ) + iname = readimport(); + fpos = prev; + return iname; +} + +void readexport( int32_t *class, int32_t *ofs, int32_t *siz, int32_t *name ) +{ + *class = readindex(); + readindex(); + if ( head->pkgver >= 60 ) fpos += 4; + *name = readindex(); + fpos += 4; + *siz = readindex(); + if ( *siz > 0 ) *ofs = readindex(); +} + +void savemusic( uint8_t *mdata, int32_t msize, int32_t name, int32_t ext ) +{ + char fname[512] = {0}; + int32_t l = 0; + char *tname = pkgfile+getname(name,&l); + strncpy(fname,tname,l); + // guess format from signature + if ( !strncmp(mdata,"IMPM",4) ) + { + printf("Format: Impulse Tracker\n"); + strcat(fname,".it"); + } + else if ( !strncmp(mdata+44,"SCRM",4) ) + { + printf("Format: Scream Tracker\n"); + strcat(fname,".s3m"); + } + else if ( !strncmp(mdata,"Extended Module: ",17) ) + { + printf("Format: Extended Module\n"); + strcat(fname,".xm"); + } + else if ( !strncmp(mdata,"OggS",4) ) + { + printf("Format: Ogg Vorbis\n"); + strcat(fname,".ogg"); + } + else + { + // fall back to what I ASSUME is an embedded file extension + // in the name table, but I could be very wrong + char *ename = pkgfile+getname(ext,&l); + printf("Guessed extension: %.*s\n",l,ename); + strcat(fname,"."); + strncat(fname,ename,l); + } + int fd = open(fname,O_CREAT|O_TRUNC|O_WRONLY,0644); + if ( !fd ) + { + printf("Cannot open %s for writing: %s\n",fname, + strerror(errno)); + return; + } + int r = 0, wn = msize; + do + { + r = write(fd,mdata,wn); + if ( r == -1 ) + { + close(fd); + printf("Write failed for file %s: %s\n",fname, + strerror(errno)); + return; + } + mdata += r; + wn -= r; + } + while ( wn > 0 ); + close(fd); +} + +int main( int argc, char **argv ) +{ + if ( argc < 2 ) return 1; + int fd = open(argv[1],O_RDONLY); + if ( fd == -1 ) + { + printf("Failed to open file %s: %s\n",argv[1],strerror(errno)); + return 1; + } + struct stat st; + fstat(fd,&st); + pkgfile = malloc(st.st_size); + memset(pkgfile,0,st.st_size); + head = (upkg_header_t*)pkgfile; + int r = 0; + do + { + r = read(fd,pkgfile+fpos,131072); + if ( r == -1 ) + { + close(fd); + free(pkgfile); + printf("Read failed for file %s: %s\n",argv[1], + strerror(errno)); + return 4; + } + fpos += r; + } + while ( r > 0 ); + close(fd); + fpos = 0; + if ( head->magic != UPKG_MAGIC ) + { + printf("File %s is not a valid unreal package!\n",argv[1]); + free(pkgfile); + return 2; + } + if ( !hasname("Music") ) + { + printf("Package %s does not contain music\n",argv[1]); + free(pkgfile); + return 4; + } + // loop through exports and search for music + fpos = head->oexports; + for ( int i=0; inexports; i++ ) + { + int32_t class, ofs, siz, name; + readexport(&class,&ofs,&siz,&name); + if ( (siz <= 0) || (class >= 0) ) continue; + // get the class name + int ismusic = 0; + class = -class-1; + if ( class > head->nimports ) continue; + int32_t l = 0; + char *n = pkgfile+getname(getimport(class),&l); + if ( strncmp(n,"Music",l) ) continue; + char *trk = pkgfile+getname(name,&l); + printf("Track found: %.*s\n",l,trk); + // begin reading data + size_t prev = fpos; + fpos = ofs; + if ( head->pkgver < 40 ) fpos += 8; + if ( head->pkgver < 60 ) fpos += 16; + int32_t prop = readindex(); + if ( prop >= head->nnames ) continue; + char *pname = pkgfile+getname(prop,&l); + if ( strncasecmp(pname,"none",l) ) continue; + int32_t ext; + if ( head->pkgver >= 120 ) + { + ext = readindex(); + fpos += 8; + } + else if ( head->pkgver >= 100 ) + { + fpos += 4; + ext = readindex(); + fpos += 4; + } + else if ( head->pkgver >= 62 ) + { + ext = readindex(); + fpos += 4; + } + else ext = readindex(); + int32_t msize = readindex(); + savemusic(pkgfile+fpos,msize,name,ext); + fpos = prev; + } + free(pkgfile); + return 0; +}