diff --git a/COPYING b/COPYING index efd1907..38a0da5 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -Copyright (c) 2020-2022 Marisa the Magician, UnSX Team +Copyright (c) 2015-2023 Marisa the Magician, UnSX Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 3b57aac..c568b32 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -Random single-file programs I've written in my spare time for small tasks. +Random small programs I've written in my spare time for small tasks. +Some of these are unfinished, but I keep the code here anyway. +Makefiles or other build scripts are not provided, so you are on your own. * **bleep:** I got bored and decided to write a pc speaker music program. * **ckmextract:** Extracts ESP and BSA from Skyrim steam workshop mod archives. @@ -8,45 +10,50 @@ Random single-file programs I've written in my spare time for small tasks. * **dood:** Reads an ENDOOM lump and mirrors every word "down the middle". * **dtexdupes:** Small tool I've used once or twice to clean up my doom projects of duplicate textures. * **endoomview:** Renders ENDOOM lumps onto a terminal. Requires an Unicode terminal with 256-color support (not xterm, basically). +* **fmod\_playbank (formerly fuck\_fmod):** Tool for playback of .fsb files. * **fontatlas:** Tool used in Demolitionist development, converts json font atlas data to plaintext for easier ZScript parsing. * **fontspread:** Tool used in Demolitionist development, converts back and forth between "compressed" font atlas texture and a linear spread of all glyphs. Useful for adding gradient effects and others, since all glyphs are properly centered. -* **fmod\_playbank (formerly fuck\_fmod):** Tool for playback of .fsb files. * **fuzrip:** Tool for awkwardly extracting audio from Creation Engine .fuz voice files. * **fuzz:** A fancy blocky noise filter using proto-AliceGL designs. -* **glfuzz:** OpenGL version of the filter. +* **glfuzz:** OpenGL version of the filter. A Vulkan version might be made at some point. +* **grabpng:** (TODO) Batch processing of grAb chunks in PNG files. * **iwad64ex:** A small, failed experiment for ripping the Hexen 64 IWAD. -* **lutconv:** A program for converting various "3D" LUT textures to actual 3D LUTs in DDS volume maps. Successor to mkvolume. Used for MariENB. Plus two additional tools for "deconverting" volume maps, and one for smoothing them out to reduce potential banding. +* **lutconv2/lutflat/lutsmooth:** A program for converting various "3D" LUT textures to actual 3D LUTs in DDS volume maps. Successor to mkvolume. Used for MariENB. Plus two additional tools for "deconverting" volume maps, and one for smoothing them out to reduce potential banding. * **mazestuff:** A dungeon generator for roguelikes. This was made as part of a commission for a friend, hence the very detailed comments. * **memrd/memsk/memwr:** Quick 'n dirty tools for memory manipulation on running programs. +* **mkbmfont:** Adaptation of the next tool for use in Godot. Creates a font atlas in BMFont format (either text or binary, depending on compile-time options). * **mkfont:** A tool originally written for the Demolitionist project. Creates GZDoom fonts from any given font, with built-in gradients and drop shadows. -* **mkfont_unr:** A tool I use to convert UE fonts exported with UTPT into fonts for GZDoom. Requires ImageMagick to be installed. +* **mkfont_unr:** A tool I use to convert UE1 fonts exported with UTPT into fonts for GZDoom. Requires ImageMagick to be installed. * **mkgauss:** Make an array of gaussian blur kernel values with passed radius and sigma. Used for shader development. * **mksoundwad:** Program used during the early days of Tim Allen Doom. Deprecated as notsanae now also replaces sounds through an OpenAL hook. * **mkssao:** Make an array of SSAO samples. Also for shader development. * **mkstartup:** Tool for creating Hexen startup screen graphics. Originally for Demolitionist, now deprecated since full-color graphics can be used. * **mkvolume:** Old program for making LUT volume maps. -* **mkwall:** A program I use on a daily basis to set my wallpaper on every Linux machine. +* **mkwall:** A program I used on a daily basis to set my wallpaper on every Linux machine. Currently abandoned, but might be eventually updated w/ Wayland support. * **osnorm:** Experiment for generating object-space normals from an .obj model. * **pcxpalex:** Extracts the palette from PCX images. * **pframes:** Short utility for automating long FrameIndex lists for MODELDEF. +* **pframes_named:** Same tool but for IQM models, where the Frame directive can be used to pair action names and frame indices. * **pngread:** Unfinished program for debugging PNG chunks. -* **schange:** Program used along with mkwall to update the wallpaper on screen geometry changes. +* **schange:** Program used along with mkwall to update the wallpaper on screen geometry changes. No longer used. * **skse_cosave:** Experiment for dumping information in SKSE co-saves. * **soapstone:** Random soapstone messages from all 3 dark souls games. Messages can be generated in bulk. -* **startuptest:** Tool for showing Hexen-style startup screens (something GZDoom can't do on Linux yet). +* **startuptest:** Tool for showing Hexen-style startup screens (Used for testing until GZDoom eventually supported these on Linux). * **totty:** Sends text from stdin to tty1. Used to send certain commands when remoting into a Raspberry Pi. * **u95/u083/u086extract:** Programs for extracting data from Unreal alpha packages. This and other Unreal tools might be shifted to another repo. +* **ucextract:** Extracts UnrealScript source files from Unreal packages. This tool is not yet functional. * **udmfvis:** dmvis clone in C for UDMF maps. No external dependencies. -* **ufontext:** companion to mkfont, for extracting UE fonts. Currently does not yet extract the textures themselves. +* **ufontext:** companion to mkfont, for extracting UE1 fonts. Currently does not yet extract the textures themselves. +* **uftxextract:** Extracts FireTextures from UE1 archives, converted to a format that can be parsed by [my ZScript library](https://git.marisa.pw/OrdinaryMagician/firetexture_m). * **umodextract:** .umod archive extractor (sort of). -* **umxunpack:** Extractor for music in UE archives, with support for Unreal 227's UMX files containing vorbis audio. +* **umxunpack:** Extractor for music in UE1 archives, with support for Unreal 227's UMX files containing vorbis audio. * **unrundeleter:** WIP program to unset the bDeleteMe flag on stuff in UE1 maps. Yes, some mappers are so hellbent on preventing modification that they delete all brushes after baking the geometry. -* **usndextract:** Extracts sounds from UE archives. -* **utxextract:** Extracts textures from UE archives. +* **usndextract:** Extracts sounds from UE1 archives. +* **utxextract:** Extracts textures from UE1 archives. * **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. -* **wavrip:** Cheap WAV file extractor that naively searchs for RIFF headers. +* **wavrip:** Cheap WAV file extractor that naively searches for RIFF headers. * **wavsmpl:** Extracts loop information from WAV files that have it, because only certain proprietary audio editors support this directly. -* **withhands:** Talk like W.D. Gaster. +* **withhands:** Talk like W.D. Gaster. Done as a bit ages ago just to tease Undertale streamers. * **zimagekver:** Quick program to extract version info from an ARM Linux kernel image. All programs and code here are under the MIT license. diff --git a/cropng.c b/cropng.c index 64d8182..5645777 100644 --- a/cropng.c +++ b/cropng.c @@ -286,9 +286,17 @@ void processpic( const char *fname ) if ( trns ) free(trns); return; } - uint8_t *odata = calloc(cropw*croph,pxsize); uint32_t ow = cropw, oh = croph; int32_t ox = x-cropx, oy = y-cropy; + if ( (ow == w) && (oh == h) && (ox == x) && (oy == y) ) + { + printf(" Nothing to crop, skipping.\n"); + free(idata); + if ( plte ) free(plte); + if ( trns ) free(trns); + return; + } + uint8_t *odata = calloc(cropw*croph,pxsize); for ( uint32_t cy=0; cy +#include +#include +#include +#include +#include +#include +#include FT_FREETYPE_H + +typedef struct +{ + int16_t fontSize; + uint8_t bitField; + uint8_t charSet; + uint16_t stretchH; + uint8_t aa, paddingUp, paddingRight, paddingDown, paddingLeft, spacingHoriz, spacingVert, outline; +} __attribute__((packed)) bmfinfo_t; + +typedef struct +{ + uint16_t lineHeight, base, scaleW, scaleH, pages; + uint8_t bitField, alphaChnl, redChnl, greenChnl, blueChnl; +} __attribute__((packed)) bmfcommon_t; + +typedef struct +{ + uint32_t id; + uint16_t x, y, width, height; + int16_t xoffset, yoffset, xadvance; + uint8_t page, chnl; +} __attribute__((packed)) bmfchar_t; + +typedef struct +{ + uint32_t unicode, glyph; + uint16_t x, y, w, h; + int16_t xofs, yofs, xadv; + uint8_t page, packed; + // channels not needed (always defaults to 15) +} packglyph_t; + +// BEGIN naive packing algo + +static int cmpglyph( const void *p1, const void *p2 ) +{ + // cast for convenience + packglyph_t *a = (packglyph_t*)p1, + *b = (packglyph_t*)p2; + // check which is taller first + int hdiff = b->h-a->h; + if ( hdiff ) return hdiff; + // if equal height, check which is wider + int wdiff = b->w-a->w; + if ( wdiff ) return wdiff; + // equal height and width, just sort by unicode value + return a->unicode-b->unicode; +} + +static void nextbox( int* boxw, int* boxh ) +{ + // increase width before height + if ( *boxw > *boxh ) *boxh <<= 1; + else *boxw <<= 1; +} + +// some sane defaults here +#ifndef PACK_TRIES +#define PACK_TRIES 100 +#endif +#ifndef MAX_PACK_SIDE +#define MAX_PACK_SIDE 1024 +#endif +#ifndef MAX_PAGES +#define MAX_PAGES 36 +#endif + +int packglyphs( packglyph_t *glyphs, uint32_t nglyphs, int* boxw, int* boxh ) +{ + // sort the glyphs first + qsort(glyphs,nglyphs,sizeof(packglyph_t),cmpglyph); + int pageno = 0; + size_t packed = 0; + for ( int i=0; i *boxw ) + { + y += lh; + x = 0; + lh = 0; + } + // past box height, bail out + if ( y+glyphs[j].h+1 > *boxh ) break; + // otherwise pack it and advance + glyphs[j].x = x; + glyphs[j].y = y; + x += glyphs[j].w+1; + // save tallest in row + if ( (glyphs[j].h+1) > lh ) lh = glyphs[j].h+1; + glyphs[j].page = pageno; + glyphs[j].packed = 1; + packed++; + } + if ( packed == nglyphs ) return 0; + // box is already at size limit, make a new page + if ( (*boxw >= MAX_PACK_SIDE) && (*boxh >= MAX_PACK_SIDE) ) + { + pageno++; + // if we're past the page limit, fail early + if ( pageno >= MAX_PAGES ) return 2; + continue; + } + // try again from scratch with a larger box + packed = 0; + for ( uint32_t j=0; jx < 0) || (g->x >= (aw-g->w)) || (g->y < 0) || (g->y >= (ah-g->h)) ) + { + oob = 1; + return; + } + uint8_t *gpos = idata; + uint8_t *apos = adata+(g->x+g->y*aw)*4; + for ( int i=0; i<(g->h); i++ ) + { + memcpy(apos,gpos,(g->w)*4); + gpos += w*4; + apos += aw*4; + } +} + +void putpixel_grayscale( uint8_t v, uint8_t a, int x, int y ) +{ + uint32_t tpos = (x+y*w)*4; + // add alpha + int alph = idata[tpos+3]; + alph += a; + if ( alph > 255 ) alph = 255; + idata[tpos+3] = alph; + // blend color + int col = idata[tpos]*(a-255); + col += v*a; + col /= 255; + idata[tpos] = col; + idata[tpos+1] = col; + idata[tpos+2] = col; +} + +void putpixel_color( uint8_t v, uint8_t a, int x, int y ) +{ + uint32_t tpos = (x+y*w)*4; + // add alpha + int alph = idata[tpos+3]; + alph += a; + if ( alph > 255 ) alph = 255; + idata[tpos+3] = alph; + // blend color (RGB) + for ( int i=0; i<3; i++ ) + { + int col = idata[tpos+i]*(a-255); + int palent = (v*palsize)/256; + col += pal[palent*3+i]*a; + col /= 255; + idata[tpos+i] = col; + } +} + +void putpixel( uint8_t v, uint8_t a, int x, int y ) +{ + if ( (x < 0) || (x >= w) || (y < 0) || (y >= h) ) + { + oob = 1; + return; + } + if ( palsize == 0 ) putpixel_grayscale(v,a,x,y); + else putpixel_color(v,a,x,y); +} + +uint8_t lerpg( float a ) +{ + if ( a >= 1. ) return 255; + if ( a <= 0. ) return 64; + return (uint8_t)(a*191+64); +} + +int draw_glyph( FT_Bitmap *bmp, uint8_t v, int px, int py, int oy ) +{ + if ( !bmp->buffer ) return 0; + int drawn = 0; + unsigned i, j; + for ( j=0; jrows; j++ ) + { + uint8_t rv = v; + // apply gradient, if any + if ( v == 255 ) + { + float a; + int ofs = j+oy; + if ( ofs < 0 ) a = 0.; + else a = ofs/(float)(h-gh); + if ( (gradient&3) == 1 ) rv = lerpg(1.-a); + else if ( (gradient&3) == 2 ) rv = lerpg(a); + else if ( (gradient&3) == 3 ) rv = lerpg((a>.5)?((1.-a)*2.):(a*2.)); + } + for ( i=0; iwidth; i++ ) + { + if ( bmp->pixel_mode == FT_PIXEL_MODE_GRAY ) + { + uint8_t a = bmp->buffer[i+j*bmp->pitch]; + drawn |= (a > 0); + putpixel(rv,a,i+px,j+py); + } + else if ( bmp->pixel_mode == FT_PIXEL_MODE_MONO ) + { + // thanks to https://stackoverflow.com/a/14905971 + unsigned p = bmp->pitch; + uint8_t *row = &bmp->buffer[p*j]; + uint8_t a = ((row[i>>3])&(128>>(i&7)))?255:0; + drawn |= (a > 0); + putpixel(rv,a,i+px,j+py); + } + } + } + return drawn; +} + +int palinv = 0; + +void loadpalette( const char *path ) +{ + FILE *f = fopen(path,"rb"); + if ( !f ) + { + fprintf(stderr,"warning: could not open palette file '%s', falling back to grayscale\n",path); + return; + } + fseek(f,0,SEEK_END); + long sz = ftell(f); + fseek(f,0,SEEK_SET); + if ( sz <= 0 ) + { + fprintf(stderr,"warning: palette is empty, falling back to grayscale\n"); + goto palend; + } + if ( !(sz%3) ) + { + // RGB8 palette + if ( sz > 768 ) + { + fprintf(stderr,"warning: palette has more than 256 entries, extra colors will be ignored\n"); + palsize = 256; + } + else palsize = sz/3; + for ( int i=0; i 1024 ) + { + fprintf(stderr,"warning: palette has more than 256 entries, extra colors will be ignored\n"); + palsize = 256; + } + else palsize = sz/4; + for ( int i=0; ipage != b->page ) + return a->page-b->page; + // sort from lowest to highest unicode value second + return a->unicode-b->unicode; +} + +// used for naming a grand total of 36 potential pages +char base36( uint8_t val ) +{ + if ( val < 10 ) return '0'+val; + else return 'A'+(val-10); +} + +int main( int argc, char **argv ) +{ + if ( argc < 4 ) + { + fprintf(stderr,"usage: mkbmfont [:face index] " + " [gradient type (0,11)] [color palette] [-palinv]\n"); + return 1; + } + if ( FT_Init_FreeType(&ftlib) ) + { + fprintf(stderr,"error: failed to init freetype library\n"); + return 2; + } + int pxsiz; + sscanf(argv[2],"%d",&pxsiz); + char bmname[256], pname[256]; + snprintf(bmname,256,"%s.fnt",argv[3]); + if ( argc > 4 ) + { + sscanf(argv[4],"%d",&gradient); + if ( (gradient < 0) || (gradient >= 12) ) + { + fprintf(stderr,"warning: gradient type out of range, falling back to type 0.\n"); + gradient = 0; + } + } + if ( !(gradient&8) ) + { + gw++; + gh++; + if ( gradient&4 ) + { + gx--; + gy--; + gw++; + gh++; + } + } + fprintf(stderr,"info: gradient selected is '%s'.\n",grads[gradient]); + if ( argc > 6 ) palinv = !strcmp(argv[6],"-palinv"); + if ( argc > 5 ) loadpalette(argv[5]); + char *fidx = strrchr(argv[1],':'); + int fnum = 0; + if ( fidx ) + { + *fidx = 0; + sscanf(fidx+1,"%d",&fnum); + } + if ( FT_New_Face(ftlib,argv[1],fnum,&fnt) ) + { + if ( fidx ) fprintf(stderr,"error: failed to open font '%s' face %d.\n",argv[1],fnum); + else fprintf(stderr,"error: failed to open font '%s'.\n",argv[1]); + return 4; + } + fprintf(stderr,"info: loaded font \'%s %s\'.\n",fnt->family_name,fnt->style_name); + int nfnt = fnt->num_faces; + if ( !fidx && (nfnt > 1) ) + { + fprintf(stderr,"info: loaded font has %d faces, you may want to load a specific one (see below).\n",nfnt); + FT_Done_Face(fnt); + for ( int i=0; ifamily_name,fnt->style_name); + FT_Done_Face(fnt); + } + FT_Done_FreeType(ftlib); + return 0; + } + if ( FT_Set_Pixel_Sizes(fnt,0,pxsiz) ) + { + if ( fnt->num_fixed_sizes <= 0 ) + { + fprintf(stderr,"error: font pixel size of '%d' not available.\n",pxsiz); + return 8; + } + fprintf(stderr,"warning: failed to set pixel size '%d', trying fixed sizes.\n",pxsiz); + int match = -1; + for ( int i=0; inum_fixed_sizes; i++ ) + { + if ( fnt->available_sizes[i].height == pxsiz ) continue; + match = i; + break; + } + if ( (match == -1) || FT_Select_Size(fnt,match) ) + { + fprintf(stderr,"error: font fixed size of '%d' not available\n",pxsiz); + fprintf(stderr,"available size(s): "); + for ( int i=0; inum_fixed_sizes; i++ ) + fprintf(stderr,"%u%s",fnt->available_sizes[i].height,(i<(fnt->num_fixed_sizes-1))?", ":".\n"); + FT_Done_Face(fnt); + FT_Done_FreeType(ftlib); + return 8; + } + } + lh = (fnt->size->metrics.height>>6); + FT_Select_Charmap(fnt,FT_ENCODING_UNICODE); + // pre-allocate + uint32_t nglyphs = MAX_GLYPHS; + packglyph_t* glyphs = calloc(nglyphs,sizeof(packglyph_t)); + uint32_t cur = 0; + // count up and populate valid glyphs + for ( uint32_t i=0; i<=MAX_UNICODE; i++ ) + { + FT_UInt glyph = FT_Get_Char_Index(fnt,i); + if ( !glyph || FT_Load_Glyph(fnt,glyph,LOADFLAGS) ) continue; + FT_Render_Glyph(fnt->glyph,RENDERMODE); + glyphs[cur].unicode = i; + glyphs[cur].glyph = glyph; + glyphs[cur].x = 0; + glyphs[cur].y = 0; + if ( !fnt->glyph->bitmap.buffer ) + { + glyphs[cur].w = 0; + glyphs[cur].h = 0; + glyphs[cur].xofs = 0; + glyphs[cur].yofs = 0; + } + else + { + glyphs[cur].w = fnt->glyph->bitmap.width+gw; + glyphs[cur].h = fnt->glyph->bitmap.rows+gh; + glyphs[cur].xofs = fnt->glyph->bitmap_left+gx; + glyphs[cur].yofs = (-fnt->glyph->bitmap_top)+gy; + } + glyphs[cur].xadv = fnt->glyph->advance.x>>6; + // calculate baseline using 'n' as reference + if ( i == 'n' ) bl = pxsiz-(-fnt->glyph->bitmap_top+fnt->glyph->bitmap.rows); + cur++; + if ( cur > nglyphs ) + { + fprintf(stderr,"warning: font somehow covers more code points than expected\n"); + break; + } + } + if ( cur <= 0 ) + { + fprintf(stderr,"error: no valid glyphs in font.\n"); + FT_Done_Face(fnt); + FT_Done_FreeType(ftlib); + return 16; + } + // reduce array size if smaller, for convenience + if ( nglyphs > cur ) + { + nglyphs = cur; + glyphs = realloc(glyphs,nglyphs*sizeof(packglyph_t)); + } + // update offsets to account for baseline + for ( uint32_t i=0; i w ) w = glyphs[i].w; + if ( glyphs[i].h > h ) h = glyphs[i].h; + } + fprintf(stderr,"info: max cell size is %dx%d.\n",w,h); + idata = calloc(4,w*h); + // third pass, pack, allocate atlas, then draw + int retval = packglyphs(glyphs,nglyphs,&aw,&ah); + if ( retval ) + { + char errormsg[][32] = {"max tries hit","max pages hit"}; + fprintf(stderr,"error: failed to pack glyphs into an adequate atlas: %s.\n",errormsg[retval-1]); + free(idata); + FT_Done_Face(fnt); + FT_Done_FreeType(ftlib); + return 32; + } + // re-sort + qsort(glyphs,nglyphs,sizeof(packglyph_t),cmpglyph_final); + adata = calloc(4,aw*ah); + int npages = 1; + for ( uint32_t i=0; iglyph,RENDERMODE); + if ( !fnt->glyph->bitmap.buffer ) continue; + int oy = (h-gh) - (2+fnt->glyph->bitmap_top); + if ( gradient&8 ) + draw_glyph(&fnt->glyph->bitmap,255,0,0,oy); + else if ( gradient&4 ) + { + // outline first + for ( int x=-1; x<=1; x++ ) + for ( int y=-1; y<=1; y++ ) + { + // ignore center + if ( !(x|y) ) continue; + draw_glyph(&fnt->glyph->bitmap,0,1+x,1+y,oy); + } + draw_glyph(&fnt->glyph->bitmap,255,1,1,oy); + } + else + { + // drop shadow first + draw_glyph(&fnt->glyph->bitmap,0,1,1,oy); + draw_glyph(&fnt->glyph->bitmap,255,0,0,oy); + } + // are we switching pages? + if ( glyphs[i].page >= npages ) + { + snprintf(pname,256,"%s%c.png",argv[3],base36(npages-1)); + writepng(pname,adata,aw,ah,aw*4); + memset(adata,0,aw*ah*4); + npages++; + } + // blit to canvas + blitglyph(glyphs+i); + // clear for next draw + memset(idata,0,w*h*4); + } + fprintf(stderr,"info: texture atlas size is %ux%u, with %d page(s).\n",aw,ah,npages); + free(idata); + if ( npages == 1 ) snprintf(pname,256,"%s.png",argv[3]); + else snprintf(pname,256,"%s%c.png",argv[3],base36(npages-1)); + writepng(pname,adata,aw,ah,aw*4); + // don't forget to write the bmfont file +#ifdef USE_BINARY_BMFONT + FILE *bmf = fopen(bmname,"wb"); + if ( !bmf ) + { + fprintf(stderr,"error: failed to open '%s' for writing: %s\n",bmname,strerror(errno)); + free(adata); + FT_Done_Face(fnt); + FT_Done_FreeType(ftlib); + return 64; + } + uint32_t bsiz = 0; + // header + fputc(66,bmf); + fputc(77,bmf); + fputc(70,bmf); + fputc(3,bmf); + // info + char fontName[256]; + snprintf(fontName,256,"%s %s",fnt->family_name,fnt->style_name); + bmfinfo_t info = { pxsiz, 2, 0, 100, 1, 0, 0, 0, 0, 1, 1, 0 }; + bsiz = sizeof(bmfinfo_t)+strlen(fontName)+1; + fputc(1,bmf); + fwrite(&bsiz,4,1,bmf); + fwrite(&info,sizeof(bmfinfo_t),1,bmf); + fwrite(fontName,strlen(fontName),1,bmf); + fputc(0,bmf); + // common + bmfcommon_t cmn = { lh, bl, aw, ah, npages, 0, 0, 4, 4, 4 }; + bsiz = sizeof(bmfcommon_t); + fputc(2,bmf); + fwrite(&bsiz,4,1,bmf); + fwrite(&cmn,sizeof(bmfcommon_t),1,bmf); + // pages + fputc(3,bmf); + if ( npages == 1 ) + { + snprintf(pname,256,"%s.png",argv[3]); + bsiz = strlen(pname)+1; + fwrite(&bsiz,4,1,bmf); + fwrite(pname,strlen(pname),1,bmf); + fputc(0,bmf); + } + else + { + snprintf(pname,256,"%s0.png",argv[3]); + bsiz = (strlen(pname)+1)*npages; + fwrite(&bsiz,4,1,bmf); + for ( int i=0; ifamily_name,fnt->style_name,pxsiz); + // common + fprintf(bmf,"common lineHeight=%u base=%u scaleW=%u scaleH=%u pages=%d packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4\n",lh,bl,aw,ah,npages); + // pages + if ( npages == 1 ) + { + snprintf(pname,256,"%s.png",argv[3]); + fprintf(bmf,"page id=0 file=\"%s\"\n",pname); + } + else for ( int i=0; i " + fprintf(stderr,"usage: mkfont [:face index] " " [gradient type (0,11)] [color palette] [-palinv]\n"); return 1; } @@ -376,12 +376,38 @@ int main( int argc, char **argv ) } if ( argc > 6 ) palinv = !strcmp(argv[6],"-palinv"); if ( argc > 5 ) loadpalette(argv[5]); - if ( FT_New_Face(ftlib,argv[1],0,&fnt) ) + char *fidx = strrchr(argv[1],':'); + int fnum = 0; + if ( fidx ) { - fprintf(stderr,"error: failed to open font '%s'\n",argv[1]); + *fidx = 0; + sscanf(fidx+1,"%d",&fnum); + } + if ( FT_New_Face(ftlib,argv[1],fnum,&fnt) ) + { + if ( fidx ) fprintf(stderr,"error: failed to open font '%s' face %d.\n",argv[1],fnum); + else fprintf(stderr,"error: failed to open font '%s'.\n",argv[1]); return 4; } fprintf(stderr,"info: loaded font \'%s %s\'.\n",fnt->family_name,fnt->style_name); + int nfnt = fnt->num_faces; + if ( !fidx && (nfnt > 1) ) + { + fprintf(stderr,"info: loaded font has %d faces, you may want to load a specific one (see below).\n",nfnt); + FT_Done_Face(fnt); + for ( int i=0; ifamily_name,fnt->style_name); + FT_Done_Face(fnt); + } + FT_Done_FreeType(ftlib); + return 0; + } if ( !strcmp(fnt->family_name,"tewi") ) tewi_hotfix = 1; else if ( !strcmp(fnt->family_name,"miniwi") ) miniwi_hotfix = 1; if ( FT_Set_Pixel_Sizes(fnt,0,pxsiz) ) @@ -481,7 +507,7 @@ int main( int argc, char **argv ) int adv2 = fnt->glyph->advance.x>>6; if ( adv2 ) adv = adv2; w = adv; - iw = w+((gradient&4)?2:1); + iw = w+((gradient&8)?0:(gradient&4)?2:1); } int xx = 0; int yy = upshift+tewi_hotfix+miniwi_hotfix; diff --git a/pngread.c b/pngread.c index 3b90365..4677d0c 100644 --- a/pngread.c +++ b/pngread.c @@ -4,7 +4,7 @@ #include #include -uint32_t endianswap( uint32_t n ) +uint32_t endianswap32( uint32_t n ) { // if we're in a big endian system, we don't need this uint16_t testme = 0x1234; @@ -15,6 +15,17 @@ uint32_t endianswap( uint32_t n ) return on; } +uint16_t endianswap16( uint16_t n ) +{ + // if we're in a big endian system, we don't need this + uint16_t testme = 0x1234; + if ( *(uint8_t*)(&testme) == 0x12 ) return n; + uint16_t on; + for ( int i=0; i<2; i++ ) + *(((uint8_t*)(&on))+i) = *(((uint8_t*)(&n))+(1-i)); + return on; +} + typedef struct { uint8_t highbitcheck; @@ -44,69 +55,465 @@ typedef struct uint8_t unit; } phys_t; +int verbose = 0; int exitval = 0; +int bailout = 0; FILE *f = 0; int nchunk = 0; -pnghead_t hdr = {0,0,0,0,0}; -pngchunk_t chk = {0,0,0,0}; // make sure this is null at the very start +pnghead_t hdr = {0,{0},{0},0,0}; +pngchunk_t chk = {0,{0},0,0}; // make sure this is null at the very start +uint8_t coldepth = 0; +uint8_t coltype = 0; uint32_t curcrc = 0; int validcrc = 0; uint8_t *tdat = 0; // accumulated data for all IDAT chunks uint32_t tdatsiz = 0; // size of it -void readIHDR( ihdr_t* ihdr ) +typedef struct { - printf(" width: %u\n height: %u\n depth: %u\n type: %u\n compression: %u\n filter: %u\n interlace: %u\n", - endianswap(ihdr->width),endianswap(ihdr->height),ihdr->depth,ihdr->type,ihdr->compression,ihdr->filter,ihdr->interlace); + char name[4]; + void(*func)(pngchunk_t *chk); +} pngfunc_t; + +void dumpData( uint8_t *dat, uint32_t len ) +{ + for ( uint32_t i=0; idata,chk->length); } -// process the whole thing -void processIDAT( void ) +const char* getcoltype( uint8_t type ) { + switch ( type ) + { + case 0: + return "Grayscale"; + case 2: + return "RGB"; + case 3: + return "Indexed"; + case 4: + return "Grayscale Alpha"; + case 6: + return "RGB Alpha"; + } + return "Invalid"; } -void readpHYs( phys_t* phys ) +const char* getcompression( uint8_t compression ) { - printf(" xppu: %u\n yppu: %u\n unit: %u\n",endianswap(phys->xppu),endianswap(phys->yppu),phys->unit); + if ( !compression ) + return "Deflate"; + return "Invalid"; } -void readtEXt( uint8_t* data, uint32_t len ) +const char* getfilter( uint8_t filter ) { - uint8_t *key = data; - uint8_t *val = data+strlen(data)+1; - uint32_t vallen = len-(val-key); + if ( !filter ) + return "Adaptive"; + return "Invalid"; +} + +const char* getinterlace( uint8_t interlace ) +{ + switch ( interlace ) + { + case 0: + return "None"; + case 1: + return "Adam7"; + } + return "Invalid"; +} + +void readIHDR( pngchunk_t *chk ) +{ + if ( chk->length != 13 ) + { + printf(" !!IHDR size is not 13, bailing out\n"); + bailout = 1; + return; + } + ihdr_t* ihdr = (ihdr_t*)(chk->data); + printf(" width: %u\n height: %u\n depth: %u\n type: %u (%s)\n" + " compression: %u (%s)\n filter: %u (%s)\n interlace: %u (%s)\n", + endianswap32(ihdr->width),endianswap32(ihdr->height), + ihdr->depth,ihdr->type,getcoltype(ihdr->type), + ihdr->compression,getcompression(ihdr->compression), + ihdr->filter,getfilter(ihdr->filter), + ihdr->interlace,getinterlace(ihdr->interlace)); + coldepth = ihdr->type; + coltype = ihdr->type; +} + +void readPLTE( pngchunk_t *chk ) +{ + if ( chk->length%3 ) + { + printf(" !!palette length not divisible by 3, may be corrupted, skipping\n"); + return; + } + unsigned ncol = chk->length/3; + for ( unsigned i=0; idata[i*3],chk->data[i*3+1],chk->data[i*3+2]); +} + +void readIDAT( pngchunk_t *chk ) +{ + if ( tdat ) tdat = realloc(tdat,tdatsiz+chk->length); + else tdat = malloc(chk->length); + memcpy(tdat+tdatsiz,chk->data,chk->length); + tdatsiz += chk->length; +} + +void readtRNS( pngchunk_t *chk ) +{ + for ( unsigned i=0; ilength; i++ ) + printf(" %d: %hhu\n",i,chk->data[i]); +} + +void readgAMA( pngchunk_t *chk ) +{ + if ( chk->length != 4 ) + { + printf(" !!gAMA size is not 4, skipping\n"); + return; + } + uint32_t rg = endianswap32(*(uint32_t*)(chk->data)); + printf(" 1/%g\n",1./(rg/100000.)); +} + +void readcHRM( pngchunk_t *chk ) +{ + if ( chk->length != 32 ) + { + printf(" !!cHRM size is not 64, skipping\n"); + return; + } + uint32_t rwx = endianswap32(*(uint32_t*)(chk->data)), + rwy = endianswap32(*(uint32_t*)(chk->data+4)), + rrx = endianswap32(*(uint32_t*)(chk->data+8)), + rry = endianswap32(*(uint32_t*)(chk->data+12)), + rgx = endianswap32(*(uint32_t*)(chk->data+16)), + rgy = endianswap32(*(uint32_t*)(chk->data+20)), + rbx = endianswap32(*(uint32_t*)(chk->data+24)), + rby = endianswap32(*(uint32_t*)(chk->data+28)); + printf(" White Point x: %g\n White Point y: %g\n" + " Red x: %g\n Red y: %g\n" + " Green x: %g\n Green y: %g\n" + " Blue x: %g\n Blue y: %g\n", + rwx/100000.,rwy/100000., + rrx/100000.,rry/100000., + rgx/100000.,rgy/100000., + rbx/100000.,rby/100000.); +} + +void readsRGB( pngchunk_t *chk ) +{ + if ( chk->length != 1 ) + { + printf(" !!sRGB size is not 1, skipping\n"); + return; + } + switch ( chk->data[0] ) + { + case 0: + printf(" Perceptual\n"); + break; + case 1: + printf(" Relative colorimetric\n"); + break; + case 2: + printf(" Saturation\n"); + break; + case 3: + printf(" Absolute colorimetric\n"); + break; + default: + printf(" Unknown\n"); + break; + } +} + +void readiCCP( pngchunk_t *chk ) +{ + // TODO process this + dumpChunk(chk); +} + +void readtEXt( pngchunk_t *chk ) +{ + char *key = (char*)chk->data; + char *val = key+strlen(key)+1; + uint32_t vallen = chk->length-(val-key); printf(" '%s' :: '%.*s'\n",key,vallen,val); } -void readchunkdata( pngchunk_t* chk ) +void readzTXt( pngchunk_t *chk ) { - if ( !strncmp(chk->type,"IHDR",4) ) - readIHDR((ihdr_t*)chk->data); - else if ( !strncmp(chk->type,"pHYs",4) ) - readpHYs((phys_t*)chk->data); - else if ( !strncmp(chk->type,"tEXt",4) ) - readtEXt(chk->data,chk->length); + // TODO decompress this stuff + dumpChunk(chk); } +void readiTXt( pngchunk_t *chk ) +{ + char *key = (char*)chk->data; + uint8_t cmpflag = chk->data[strlen(key)+1]; + if ( cmpflag ) + { + printf(" !!compressed iTXt not supported, skipping"); + return; + } + char *lang = (char*)(chk->data+strlen(key)+3); + char *kword = (char*)(chk->data+strlen(key)+3+strlen(lang)+1); + char *text = (char*)(chk->data+strlen(key)+3+strlen(lang)+1+strlen(kword)+1); + uint32_t textlen = chk->length-(text-key); + printf(" '%s' :: ",key); + if ( lang[0] ) printf("[%s] ",lang); + if ( kword[0] ) printf("'%s' ",kword); + if ( lang[0] || kword[0] ) printf(":: "); + printf("'%.*s'\n",textlen,text); +} + +void readbKGD( pngchunk_t *chk ) +{ + switch ( coltype ) + { + case 3: + if ( chk->length != 1 ) + { + printf(" !!bKGD size is not 1, skipping\n"); + return; + } + printf(" BG: %hhu\n",chk->data[0]); + break; + case 0: + case 4: + if ( chk->length != 2 ) + { + printf(" !!bKGD size is not 2, skipping\n"); + return; + } + printf(" BG: %hu\n",endianswap16(*(uint16_t*)(chk->data))); + break; + case 2: + case 6: + if ( chk->length != 6 ) + { + printf(" !!bKGD size is not 6, skipping\n"); + return; + } + printf(" BG: (%hu,%hu,%hu)\n", + endianswap16(*(uint16_t*)(chk->data)), + endianswap16(*(uint16_t*)(chk->data+2)), + endianswap16(*(uint16_t*)(chk->data+4))); + break; + default: + printf(" !!bKGD present for unhandled color type %d, skipping\n",coltype); + break; + } +} + +void readpHYs( pngchunk_t *chk ) +{ + if ( chk->length != 9 ) + { + printf(" !!pHYs size is not 9, skipping\n"); + return; + } + phys_t* phys = (phys_t*)(chk->data); + printf(" xppu: %u\n yppu: %u\n unit: %u\n",endianswap32(phys->xppu),endianswap32(phys->yppu),phys->unit); +} + +void readsBIT( pngchunk_t *chk ) +{ + switch ( coltype ) + { + case 0: + if ( chk->length != 1 ) + { + printf(" !!sBIT size is not 1, skipping\n"); + return; + } + printf(" %hhu\n",chk->data[0]); + break; + case 2: + case 3: + if ( chk->length != 3 ) + { + printf(" !!sBIT size is not 3, skipping\n"); + return; + } + printf(" (%hhu,%hhu,%hhu)\n",chk->data[0],chk->data[1],chk->data[2]); + break; + case 4: + if ( chk->length != 2 ) + { + printf(" !!sBIT size is not 2, skipping\n"); + return; + } + printf(" (%hhu,%hhu)\n",chk->data[0],chk->data[1]); + break; + case 6: + if ( chk->length != 4 ) + { + printf(" !!sBIT size is not 4, skipping\n"); + return; + } + printf(" (%hhu,%hhu,%hhu,%hhu)\n",chk->data[0],chk->data[1],chk->data[2],chk->data[3]); + break; + default: + printf(" !!sBIT present for unhandled color type %d, skipping\n",coltype); + break; + } +} + +void readsPLT( pngchunk_t *chk ) +{ + char *name = (char*)chk->data; + uint8_t depth = chk->data[strlen(name)+1]; + printf(" suggested palette: %s\n sample depth: %hhu\n",name,depth); + int div; + if ( depth == 16 ) + div = 10; + else if ( depth == 8 ) + div = 6; + else + { + printf(" !!invalid depth of %u, skipping\n",depth); + return; + } + uint32_t psiz = (chk->length-(strlen(name)+2)); + if ( psiz%div ) + { + printf(" !!palette length not divisible by %d, skipping\n",div); + return; + } + unsigned ncol = psiz/div; + uint8_t *pdat = chk->data+(strlen(name)+2); + for ( unsigned i=0; ilength%2 ) + { + printf(" !!hIST size not divisible by two, skipping\n"); + return; + } + unsigned ncol = chk->length/2; + for ( unsigned i=0; idata+i*2))); +} + +void readtIME( pngchunk_t *chk ) +{ + if ( chk->length != 7 ) + { + printf(" !!tIME size is not 7, skipping\n"); + return; + } + printf(" %hu-%hhu-%hhu %hhu:%hhu:%hhu\n", + endianswap16(*(uint16_t*)(chk->data)),chk->data[2],chk->data[3], + chk->data[4],chk->data[5],chk->data[6]); +} + +void readgrAb( pngchunk_t *chk ) +{ + if ( chk->length != 8 ) + { + printf(" !!grAb size is not 8, skipping\n"); + return; + } + uint32_t rx = endianswap32(*(uint32_t*)(chk->data)), ry = endianswap32(*(uint32_t*)(chk->data+4)); + int32_t x = *(int32_t*)(&rx), y = *(int32_t*)(&ry); + printf(" %+d,%+d\n",x,y); +} + +#define NUMREADERS 20 + +pngfunc_t readers[NUMREADERS] = +{ + // critical + {"IHDR",readIHDR}, + {"PLTE",readPLTE}, + {"IDAT",readIDAT}, + {"IEND",0}, + // transparency + {"tRNS",readtRNS}, + // colorspace + {"gAMA",readgAMA}, + {"cHRM",readcHRM}, + {"sRGB",readsRGB}, + {"iCCP",readiCCP}, + // text + {"tEXt",readtEXt}, + {"zTXt",readzTXt}, + {"iTXt",readiTXt}, + // misc + {"bKGD",readbKGD}, + {"pHYs",readpHYs}, + {"sBIT",readsBIT}, + {"sPLT",readsPLT}, + {"hIST",readhIST}, + {"tIME",readtIME}, + // zdoom + {"grAb",readgrAb}, + {"alPh",0} +}; + +void readchunkdata( pngchunk_t* chk ) +{ + for ( int i=0; itype,readers[i].name,4) ) continue; + if ( readers[i].func ) readers[i].func(chk); + return; + } + printf(" !!no reader for this chunk type\n"); +} + +// TODO process the whole thing (might not bother actually) +void processIDAT( void ) +{ + if ( !verbose ) return; + printf("full datastream dump follows:\n"); + dumpData(tdat,tdatsiz); +} int main( int argc, char **argv ) { - if ( argc < 2 ) + if ( (argc > 1) && (!strncmp(argv[1],"-v",2) || !strncmp(argv[1],"--verbose",9)) ) + verbose = 1; + if ( argc < (2+verbose) ) { printf("No file supplied.\n"); exitval = 0; goto endmii; } - f = fopen(argv[1],"rb"); + f = fopen(argv[1+verbose],"rb"); if ( !f ) { printf("Failed to open file.\n"); @@ -146,7 +553,7 @@ int main( int argc, char **argv ) } readchunk: fread(&chk,1,8,f); // read size and type first - chk.length = endianswap(chk.length); // swap from BE + chk.length = endianswap32(chk.length); // swap from BE if ( (nchunk == 0) && strncmp(chk.type,"IHDR",4) ) { printf("First chunk is not IHDR.\n"); @@ -158,21 +565,33 @@ readchunk: else chk.data = malloc(chk.length); fread(chk.data,1,chk.length,f); // read data fread(&(chk.crc),1,4,f); // read CRC - chk.crc = endianswap(chk.crc); - curcrc = crc32(0,chk.type,4); + chk.crc = endianswap32(chk.crc); + curcrc = crc32(0,(uint8_t*)chk.type,4); if ( chk.length ) curcrc = crc32(curcrc,chk.data,chk.length); validcrc = (curcrc == chk.crc); printf("%.4s chunk of length %u (CRC32: %08X, %s).\n",chk.type,chk.length,chk.crc,validcrc?"OK":"FAILED"); if ( !validcrc ) printf(" CRC32 ACTUAL: %08X\n",curcrc); else readchunkdata(&chk); nchunk++; + if ( bailout ) goto endmii; if ( strncmp(chk.type,"IEND",4) ) goto readchunk; + if ( tdat ) processIDAT(); long cpos = ftell(f); fseek(f,0,SEEK_END); long epos = ftell(f); - if ( epos > cpos ) printf("%lu bytes of extra data after IEND.\n",epos-cpos); + if ( epos > cpos ) + { + printf(" !!there are %lu bytes of extra data after IEND\n",epos-cpos); + tdatsiz = epos-cpos; + if ( tdat ) tdat = realloc(tdat,tdatsiz); + else tdat = malloc(tdatsiz); + fseek(f,cpos,SEEK_SET); + fread(tdat,1,tdatsiz,f); + if ( verbose ) dumpData(tdat,tdatsiz); + } endmii: if ( f ) fclose(f); if ( chk.data ) free(chk.data); + if ( tdat ) free(tdat); return exitval; } diff --git a/soapstone.c b/soapstone.c index 7616b6e..ef92316 100644 --- a/soapstone.c +++ b/soapstone.c @@ -9,874 +9,10 @@ #include #include -const char templates1[13][32] = -{ - "%s ahead", - "Be wary of %s", - "Try %s", - "Need %s", - "Imminent %s...", - "Weakness: %s", - "%s", - "%s?", - "Good luck", - "I did it!", - "Here!", - "I can't take this...", - "Praise the Sun!" -}; - -const char words1[173][32] = -{ - "Enemy", - "Tough enemy", - "Hollow", - "Soldier", - "Knight", - "Sniper", - "Caster", - "Giant", - "Skeleton", - "Ghost", - "Bug", - "Poison bug", - "Lizard", - "Drake", - "Flier", - "Golem", - "Statue", - "Monster", - "Strange creature", - "Demon", - "Darkwraith", - "Dragon", - "Boss", - "Saint", - "Wretch", - "Charmer", - "Miscreant", - "Liar", - "Fatty", - "Beanpole", - "Merchant", - "Blacksmith", - "Master", - "Prisoner", - "Bonfire", - "Fog wall", - "Humanity", - "Lever", - "Switch", - "Key", - "Treasure", - "Chest", - "Weapon", - "Shield", - "Projectile", - "Armour", - "Item", - "Ring", - "Sorcery scroll", - "Pyromancy scroll", - "Miracle scroll", - "Ember", - "Trap", - "Covenant", - "Amazing key", - "Amazing treasure", - "Amazing chest", - "Amazing weapon", - "Amazing armour", - "Amazing item", - "Amazing ring", - "Amazing sorcery scroll", - "Amazing pyromancy scroll", - "Amazing miracle scroll", - "Amazing ember", - "Amazing trap", - "Close-ranged battle", - "Ranged battle", - "Eliminating one at a time", - "Luring it out", - "Beating to a pulp", - "Lying in ambush", - "Stealth", - "Mimicry", - "Pincer attack", - "Hitting them in one swoop", - "Fleeing", - "Charging", - "Stabbing in the back", - "Sweeping attack", - "Shield breaking", - "Head shots", - "Sorcery", - "Pyromancy", - "Miracles", - "Jumping off", - "Sliding down", - "Dashing through", - "Rolling", - "Backstepping", - "Jumping", - "Attacking", - "Holding with both hands", - "Kicking", - "A plunging attack", - "Blocking", - "Parrying", - "Locking-on", - "Path", - "Hidden path", - "Shortcut", - "Detour", - "Illusory wall", - "Shortcut", - "Dead end", - "Swamp", - "Lava", - "Forest", - "Cave", - "Labyrinth", - "Safe zone", - "Danger zone", - "Sniper spot", - "Bright spot", - "Dark spot", - "Open area", - "Tight spot", - "Hiding place", - "Exchange", - "Gorgeous view", - "Fall", - "Front", - "Back", - "Left", - "Right", - "Up", - "Down", - "Feet", - "Head", - "Back", - "Head", - "Neck", - "Stomach", - "Back", - "Arm", - "Leg", - "Heel", - "Rear", - "Tail", - "Wings", - "Anywhere", - "Strike", - "Thrust", - "Slash", - "Magic", - "Fire", - "Lightning", - "Critical hits", - "Bleeding", - "Poison", - "Strong poison", - "Curses", - "Divine", - "Occult", - "Crystal", - "Chance", - "Hint", - "Secret", - "Happiness", - "Sorrow", - "Life", - "Death", - "Undead", - "Elation", - "Grief", - "Hope", - "Despair", - "Light", - "Dark", - "Bravery", - "Resignation", - "Comfort", - "Tears" -}; - -const char templates2[11][32] = -{ - "%s ahead", - "%s required ahead", - "be wary of %s", - "try %s", - "weakness: %s", - "visions of %s...", - "%s", - "%s!", - "%s?", - "%s...", - "hurrah for %s!", -}; - -const char conjunctions2[7][32] = -{ - " and then ", - " but ", - " therefore ", - " in short ", - " or ", - " by the way ", - ", " -}; - -const char words2[247][32] = -{ - "enemy", - "monster", - "lesser foe", - "tough enemy", - "boss", - "Hollow", - "skeleton", - "ghost", - "bug", - "Gyrm", - "beast", - "giant", - "dwarf", - "sniper", - "caster", - "duo", - "trio", - "saint", - "wretch", - "charmer", - "poor soul", - "oddball", - "nimble one", - "laggard", - "moneybags", - "beggar", - "miscreant", - "liar", - "fatty", - "beanpole", - "merchant", - "artisan", - "master", - "friend", - "ally", - "Dark Spirit", - "Phantom", - "Shade", - "bonfire", - "fog wall", - "lever", - "switch", - "key", - "trap", - "torch", - "door", - "treasure", - "chest", - "something", - "quite something", - "weapon", - "shield", - "projectile", - "armor", - "item", - "ring", - "scroll", - "ore", - "message", - "bloodstain", - "illusion", - "close-ranged battle", - "ranged battle", - "eliminating one at a time", - "luring it out", - "beating to a pulp", - "ambush", - "pincer attack", - "hitting them in one swoop", - "dual-wielding", - "stealth", - "mimicry", - "fleeing", - "charging", - "jumping off", - "dashing through", - "circling around", - "trapping inside", - "rescue", - "sorcery", - "pyromancy", - "miracles", - "hexes", - "pure luck", - "prudence", - "brief respite", - "play dead", - "jog", - "dash", - "rolling", - "backstepping", - "jumping", - "attacking", - "jump attack", - "dash attack", - "counter attack", - "stabbing in the back", - "guard stun & stab", - "parry stun & stab", - "plunging attack", - "sweeping attack", - "shield breaking", - "blocking", - "parrying", - "spell parry", - "locking-on", - "no lock-on", - "two-handing", - "gesture", - "control", - "destroy", - "boulder", - "lava", - "poison gas", - "enemy horde", - "forest", - "cave", - "arena", - "hidden path", - "detour", - "shortcut", - "dead end", - "labyrinth", - "hole", - "bright spot", - "dark spot", - "open area", - "tight spot", - "safe zone", - "danger zone", - "sniper spot", - "hiding place", - "illusory wall", - "ladder", - "lift", - "exchange", - "gorgeous view", - "looking away", - "overconfidence", - "slip-up", - "oversight", - "fatigue", - "bad luck", - "inattention", - "loss of stamina", - "front", - "back", - "left", - "right", - "up", - "down", - "below", - "above", - "behind", - "head", - "neck", - "stomach", - "back", - "arm", - "leg", - "rear", - "tail", - "wings", - "anywhere", - "tongue", - "right arm", - "left arm", - "right leg", - "left leg", - "right side", - "left side", - "pincer", - "wheel", - "core", - "horse", - "strike", - "thrust", - "slash", - "magic", - "sorcery", - "fire", - "lightning", - "critical hits", - "bleeding", - "poison", - "toxic", - "curse", - "equipment breakage", - "chance", - "quagmire", - "hint", - "secret", - "happiness", - "misfortune", - "life", - "death", - "joy", - "sadness", - "tears", - "hope", - "despair", - "victory", - "defeat", - "light", - "dark", - "bravery", - "confidence", - "vigor", - "revenge", - "resignation", - "overwhelming", - "regret", - "pointless", - "man", - "woman", - "recklessness", - "composure", - "guts", - "comfort", - "silence", - "good luck", - "fine work", - "I did it!", - "I've failed...", - "here!", - "not here!", - "I can't take this...", - "don't you dare!", - "do it!", - "look carefully", - "listen carefully", - "think carefully", - "this place again?", - "now the real fight begins", - "keep moving", - "pull back", - "give it up", - "don't give up", - "help me...", - "impossible...", - "bloody expensive...", - "nice job", - "let me out of here...", - "stay calm", - "like a dream...", - "are you ready?", - "praise the Sun!" -}; - -const char templates3[17][32] = -{ - "%s ahead", - "No %s ahead", - "%s required ahead", - "be wary of %s", - "try %s", - "Could this be a %s?", - "If only I had a %s...", - "visions of %s...", - "Time for %s", - "%s", - "%s!", - "%s?", - "%s...", - "Huh. It's a %s...", - "praise the %s!", - "Let there be %s", - "Ahh, %s..." -}; - -const char conjunctions3[10][32] = -{ - " and then ", - " but ", - " therefore ", - " in short ", - " or ", - " only ", - " by the way ", - " so to speak ", - " all the more ", - ", ", -}; - -const char gestures3[33][32] = -{ - "Point forward", - "Point up", - "Point down", - "Wave", - "Beckon", - "Call over", - "Welcome", - "Applause", - "Quiet Resolve", - "Jump for joy", - "Joy", - "Rejoice", - "Hurrah!", - "Praise the Sun", - "My thanks!", - "Bow", - "Proper bow", - "Dignified bow", - "Duel bow", - "Legion Etiquette", - "Darkmoon Loyalty", - "By my sword", - "Prayer", - "Silent Ally", - "Rest", - "Collapse", - "Patches Squat", - "Prostration", - "Toast", - "Sleep", - "Curl up", - "Stretch out", - "Path of the Dragon" -}; - -const char words3[322][32] = -{ - "enemy", - "monster", - "mob enemy", - "tough enemy", - "critical foe", - "Hollow", - "pilgrim", - "prisoner", - "monstrosity", - "skeleton", - "ghost", - "beast", - "lizard", - "bug", - "grub", - "crab", - "dwarf", - "giant", - "demon", - "dragon", - "knight", - "sellsword", - "warrior", - "herald", - "bandit", - "assassin", - "sorcerer", - "pyromancer", - "cleric", - "deprived", - "sniper", - "duo", - "trio", - "you", - "you bastard", - "good fellow", - "saint", - "wretch", - "charmer", - "poor soul", - "oddball", - "nimble one", - "laggard", - "moneybags", - "beggar", - "miscreant", - "liar", - "fatty", - "beanpole", - "youth", - "elder", - "old codger", - "old dear", - "miscreant", - "artisan", - "master", - "sage", - "champion", - "Lord of Cinder", - "king", - "queen", - "prince", - "princess", - "angel", - "god", - "friend", - "ally", - "spouse", - "covenantor", - "Phantom", - "Dark Spirit", - "bonfire", - "ember", - "fog wall", - "lever", - "contraption", - "key", - "trap", - "torch", - "door", - "treasure", - "chest", - "something", - "quite something", - "rubbish", - "filth", - "weapon", - "shield", - "projectile", - "armor", - "item", - "ring", - "ore", - "coal", - "transposing kiln", - "scroll", - "umbral ash", - "throne", - "rite", - "coffin", - "cinder", - "ash", - "moon", - "eye", - "brew", - "soup", - "message", - "bloodstain", - "illusion", - "close-ranged battle", - "ranged battle", - "eliminating one at a time", - "luring it out", - "beating to a pulp", - "ambush", - "pincer attack", - "hitting them in one swoop", - "dual-wielding", - "stealth", - "mimicry", - "fleeing", - "charging", - "jumping off", - "dashing through", - "circling around", - "trapping inside", - "rescue", - "Skill", - "sorcery", - "pyromancy", - "miracles", - "pure luck", - "prudence", - "brief respite", - "play dead", - "jog", - "dash", - "rolling", - "backstepping", - "jumping", - "attacking", - "jump attack", - "dash attack", - "counter attack", - "stabbing in the back", - "guard stun & stab", - "plunging attack", - "sweeping attack", - "shield breaking", - "blocking", - "parrying", - "locking-on", - "no lock-on", - "two-handing", - "gesture", - "control", - "destroy", - "boulder", - "lava", - "poison gas", - "enemy horde", - "forest", - "swamp", - "cave", - "shortcut", - "detour", - "hidden path", - "secret passage", - "dead end", - "labyrinth", - "hole", - "bright spot", - "dark spot", - "open area", - "tight spot", - "safe zone", - "danger zone", - "sniper spot", - "hiding place", - "illusory wall", - "ladder", - "lift", - "gorgeous view", - "looking away", - "overconfidence", - "slip-up", - "oversight", - "fatigue", - "bad luck", - "inattention", - "loss of stamina", - "chance encounter", - "planned encounter", - "front", - "back", - "left", - "right", - "up", - "down", - "below", - "above", - "behind", - "head", - "neck", - "stomach", - "back", - "arm", - "finger", - "leg", - "rear", - "tail", - "wings", - "anywhere", - "tongue", - "right arm", - "left arm", - "thumb", - "indexfinger", - "longfinger", - "ringfinger", - "smallfinger", - "right leg", - "left leg", - "right side", - "left side", - "pincer", - "wheel", - "core", - "mount", - "regular", - "strike", - "thrust", - "slash", - "magic", - "crystal", - "fire", - "chaos", - "lightning", - "blessing", - "dark", - "critical hits", - "bleeding", - "poison", - "toxic", - "frost", - "curse", - "equipment breakage", - "chance", - "quagmire", - "hint", - "secret", - "sleeptalk", - "happiness", - "misfortune", - "life", - "death", - "demise", - "joy", - "fury", - "agony", - "sadness", - "tears", - "loyalty", - "betrayal", - "hope", - "despair", - "fear", - "losing sanity", - "victory", - "defeat", - "sacrifice", - "light", - "dark", - "bravery", - "confidence", - "vigor", - "revenge", - "resignation", - "overwhelming", - "regret", - "pointless", - "man", - "woman", - "friendship", - "love", - "recklessness", - "composure", - "guts", - "comfort", - "silence", - "deep", - "good luck", - "fine work", - "I did it!", - "I've failed...", - "here!", - "not here!", - "I can't take this...", - "lonely...", - "don't you dare!", - "do it!", - "look carefully", - "listen carefully", - "think carefully", - "this place again?", - "now the real fight begins", - "you don't deserve this", - "keep moving", - "pull back", - "give it up", - "don't give up", - "help me...", - "impossible...", - "bloody expensive...", - "let me out of here...", - "stay calm", - "like a dream...", - "seems familiar...", - "are you ready?", - "it'll happen to you too", - "praise the Sun!", - "may the flames guide thee" -}; +#include "soapstone_ds1.h" +#include "soapstone_ds2.h" +#include "soapstone_ds3.h" +//#include "soapstone_er.h" void print_soapstone_ds1( void ) { @@ -890,16 +26,16 @@ void print_soapstone_ds1( void ) void print_soapstone_ds2( void ) { - int template = rand()%11; - int word = rand()%(((template>5)&&(template<10))?247:220); + int template = rand()%DS2_TEMPLATES; + int word = rand()%(((template>5)&&(template<10))?DS2_WORDS:DS2_WORDS2); char mesg[256]; sprintf(mesg,templates2[template],words2[word]); if ( rand()&1 ) { char mesg2[256]; - int template2 = rand()%11; - int word2 = rand()%(((template2>5)&&(template2<10))?247:220); - int conj = rand()%7; + int template2 = rand()%DS2_TEMPLATES; + int word2 = rand()%(((template2>5)&&(template2<10))?DS2_WORDS:DS2_WORDS2); + int conj = rand()%DS2_CONJUNCTIONS; sprintf(mesg2,templates2[template2],words2[word2]); printf("%s%s%s\n",mesg,conjunctions2[conj],mesg2); } @@ -908,17 +44,17 @@ void print_soapstone_ds2( void ) void print_soapstone_ds3( void ) { - if ( rand()&1 ) printf("[%s] ",gestures3[rand()%33]); - int template = rand()%17; - int word = rand()%322; + if ( rand()&1 ) printf("[%s] ",gestures3[rand()%DS3_GESTURES]); + int template = rand()%DS3_TEMPLATES; + int word = rand()%DS3_WORDS; char mesg[256]; sprintf(mesg,templates3[template],words3[word]); if ( rand()&1 ) { char mesg2[256]; - int template2 = rand()%17; - int word2 = rand()%211; - int conj = rand()%10; + int template2 = rand()%DS3_TEMPLATES; + int word2 = rand()%DS3_WORDS2; + int conj = rand()%DS3_CONJUNCTIONS; sprintf(mesg2,templates3[template2],words3[word2]); printf("%s%s%s\n",mesg,conjunctions3[conj],mesg2); } diff --git a/soapstone_ds1.h b/soapstone_ds1.h new file mode 100644 index 0000000..e3d6a3f --- /dev/null +++ b/soapstone_ds1.h @@ -0,0 +1,197 @@ +// Dark Souls messages + +#define DS1_TEMPLATES 13 +const char templates1[DS1_TEMPLATES][32] = +{ + "%s ahead", + "Be wary of %s", + "Try %s", + "Need %s", + "Imminent %s...", + "Weakness: %s", + "%s", + "%s?", + "Good luck", + "I did it!", + "Here!", + "I can't take this...", + "Praise the Sun!" +}; + +#define DS1_WORDS 173 +const char words1[DS1_WORDS][32] = +{ + "Enemy", + "Tough enemy", + "Hollow", + "Soldier", + "Knight", + "Sniper", + "Caster", + "Giant", + "Skeleton", + "Ghost", + "Bug", + "Poison bug", + "Lizard", + "Drake", + "Flier", + "Golem", + "Statue", + "Monster", + "Strange creature", + "Demon", + "Darkwraith", + "Dragon", + "Boss", + "Saint", + "Wretch", + "Charmer", + "Miscreant", + "Liar", + "Fatty", + "Beanpole", + "Merchant", + "Blacksmith", + "Master", + "Prisoner", + "Bonfire", + "Fog wall", + "Humanity", + "Lever", + "Switch", + "Key", + "Treasure", + "Chest", + "Weapon", + "Shield", + "Projectile", + "Armour", + "Item", + "Ring", + "Sorcery scroll", + "Pyromancy scroll", + "Miracle scroll", + "Ember", + "Trap", + "Covenant", + "Amazing key", + "Amazing treasure", + "Amazing chest", + "Amazing weapon", + "Amazing armour", + "Amazing item", + "Amazing ring", + "Amazing sorcery scroll", + "Amazing pyromancy scroll", + "Amazing miracle scroll", + "Amazing ember", + "Amazing trap", + "Close-ranged battle", + "Ranged battle", + "Eliminating one at a time", + "Luring it out", + "Beating to a pulp", + "Lying in ambush", + "Stealth", + "Mimicry", + "Pincer attack", + "Hitting them in one swoop", + "Fleeing", + "Charging", + "Stabbing in the back", + "Sweeping attack", + "Shield breaking", + "Head shots", + "Sorcery", + "Pyromancy", + "Miracles", + "Jumping off", + "Sliding down", + "Dashing through", + "Rolling", + "Backstepping", + "Jumping", + "Attacking", + "Holding with both hands", + "Kicking", + "A plunging attack", + "Blocking", + "Parrying", + "Locking-on", + "Path", + "Hidden path", + "Shortcut", + "Detour", + "Illusory wall", + "Shortcut", + "Dead end", + "Swamp", + "Lava", + "Forest", + "Cave", + "Labyrinth", + "Safe zone", + "Danger zone", + "Sniper spot", + "Bright spot", + "Dark spot", + "Open area", + "Tight spot", + "Hiding place", + "Exchange", + "Gorgeous view", + "Fall", + "Front", + "Back", + "Left", + "Right", + "Up", + "Down", + "Feet", + "Head", + "Back", + "Head", + "Neck", + "Stomach", + "Back", + "Arm", + "Leg", + "Heel", + "Rear", + "Tail", + "Wings", + "Anywhere", + "Strike", + "Thrust", + "Slash", + "Magic", + "Fire", + "Lightning", + "Critical hits", + "Bleeding", + "Poison", + "Strong poison", + "Curses", + "Divine", + "Occult", + "Crystal", + "Chance", + "Hint", + "Secret", + "Happiness", + "Sorrow", + "Life", + "Death", + "Undead", + "Elation", + "Grief", + "Hope", + "Despair", + "Light", + "Dark", + "Bravery", + "Resignation", + "Comfort", + "Tears" +}; diff --git a/soapstone_ds2.h b/soapstone_ds2.h new file mode 100644 index 0000000..610f88b --- /dev/null +++ b/soapstone_ds2.h @@ -0,0 +1,282 @@ +// Dark Souls 2 messages + +#define DS2_TEMPLATES 11 +const char templates2[DS2_TEMPLATES][32] = +{ + "%s ahead", + "%s required ahead", + "be wary of %s", + "try %s", + "weakness: %s", + "visions of %s...", + "%s", + "%s!", + "%s?", + "%s...", + "hurrah for %s!", +}; + +#define DS2_CONJUNCTIONS 7 +const char conjunctions2[DS2_CONJUNCTIONS][32] = +{ + " and then ", + " but ", + " therefore ", + " in short ", + " or ", + " by the way ", + ", " +}; + +#define DS2_WORDS 247 +#define DS2_WORDS2 220 +const char words2[DS2_WORDS][32] = +{ + "enemy", + "monster", + "lesser foe", + "tough enemy", + "boss", + "Hollow", + "skeleton", + "ghost", + "bug", + "Gyrm", + "beast", + "giant", + "dwarf", + "sniper", + "caster", + "duo", + "trio", + "saint", + "wretch", + "charmer", + "poor soul", + "oddball", + "nimble one", + "laggard", + "moneybags", + "beggar", + "miscreant", + "liar", + "fatty", + "beanpole", + "merchant", + "artisan", + "master", + "friend", + "ally", + "Dark Spirit", + "Phantom", + "Shade", + "bonfire", + "fog wall", + "lever", + "switch", + "key", + "trap", + "torch", + "door", + "treasure", + "chest", + "something", + "quite something", + "weapon", + "shield", + "projectile", + "armor", + "item", + "ring", + "scroll", + "ore", + "message", + "bloodstain", + "illusion", + "close-ranged battle", + "ranged battle", + "eliminating one at a time", + "luring it out", + "beating to a pulp", + "ambush", + "pincer attack", + "hitting them in one swoop", + "dual-wielding", + "stealth", + "mimicry", + "fleeing", + "charging", + "jumping off", + "dashing through", + "circling around", + "trapping inside", + "rescue", + "sorcery", + "pyromancy", + "miracles", + "hexes", + "pure luck", + "prudence", + "brief respite", + "play dead", + "jog", + "dash", + "rolling", + "backstepping", + "jumping", + "attacking", + "jump attack", + "dash attack", + "counter attack", + "stabbing in the back", + "guard stun & stab", + "parry stun & stab", + "plunging attack", + "sweeping attack", + "shield breaking", + "blocking", + "parrying", + "spell parry", + "locking-on", + "no lock-on", + "two-handing", + "gesture", + "control", + "destroy", + "boulder", + "lava", + "poison gas", + "enemy horde", + "forest", + "cave", + "arena", + "hidden path", + "detour", + "shortcut", + "dead end", + "labyrinth", + "hole", + "bright spot", + "dark spot", + "open area", + "tight spot", + "safe zone", + "danger zone", + "sniper spot", + "hiding place", + "illusory wall", + "ladder", + "lift", + "exchange", + "gorgeous view", + "looking away", + "overconfidence", + "slip-up", + "oversight", + "fatigue", + "bad luck", + "inattention", + "loss of stamina", + "front", + "back", + "left", + "right", + "up", + "down", + "below", + "above", + "behind", + "head", + "neck", + "stomach", + "back", + "arm", + "leg", + "rear", + "tail", + "wings", + "anywhere", + "tongue", + "right arm", + "left arm", + "right leg", + "left leg", + "right side", + "left side", + "pincer", + "wheel", + "core", + "horse", + "strike", + "thrust", + "slash", + "magic", + "sorcery", + "fire", + "lightning", + "critical hits", + "bleeding", + "poison", + "toxic", + "curse", + "equipment breakage", + "chance", + "quagmire", + "hint", + "secret", + "happiness", + "misfortune", + "life", + "death", + "joy", + "sadness", + "tears", + "hope", + "despair", + "victory", + "defeat", + "light", + "dark", + "bravery", + "confidence", + "vigor", + "revenge", + "resignation", + "overwhelming", + "regret", + "pointless", + "man", + "woman", + "recklessness", + "composure", + "guts", + "comfort", + "silence", + "good luck", + "fine work", + "I did it!", + "I've failed...", + "here!", + "not here!", + "I can't take this...", + "don't you dare!", + "do it!", + "look carefully", + "listen carefully", + "think carefully", + "this place again?", + "now the real fight begins", + "keep moving", + "pull back", + "give it up", + "don't give up", + "help me...", + "impossible...", + "bloody expensive...", + "nice job", + "let me out of here...", + "stay calm", + "like a dream...", + "are you ready?", + "praise the Sun!" +}; diff --git a/soapstone_ds3.h b/soapstone_ds3.h new file mode 100644 index 0000000..0e64d42 --- /dev/null +++ b/soapstone_ds3.h @@ -0,0 +1,404 @@ +// Dark Souls 3 messages + +#define DS3_TEMPLATES 17 +const char templates3[DS3_TEMPLATES][32] = +{ + "%s ahead", + "No %s ahead", + "%s required ahead", + "be wary of %s", + "try %s", + "Could this be a %s?", + "If only I had a %s...", + "visions of %s...", + "Time for %s", + "%s", + "%s!", + "%s?", + "%s...", + "Huh. It's a %s...", + "praise the %s!", + "Let there be %s", + "Ahh, %s..." +}; + +#define DS3_CONJUNCTIONS 10 +const char conjunctions3[DS3_CONJUNCTIONS][32] = +{ + " and then ", + " but ", + " therefore ", + " in short ", + " or ", + " only ", + " by the way ", + " so to speak ", + " all the more ", + ", ", +}; + +#define DS3_GESTURES 33 +const char gestures3[DS3_GESTURES][32] = +{ + "Point forward", + "Point up", + "Point down", + "Wave", + "Beckon", + "Call over", + "Welcome", + "Applause", + "Quiet Resolve", + "Jump for joy", + "Joy", + "Rejoice", + "Hurrah!", + "Praise the Sun", + "My thanks!", + "Bow", + "Proper bow", + "Dignified bow", + "Duel bow", + "Legion Etiquette", + "Darkmoon Loyalty", + "By my sword", + "Prayer", + "Silent Ally", + "Rest", + "Collapse", + "Patches Squat", + "Prostration", + "Toast", + "Sleep", + "Curl up", + "Stretch out", + "Path of the Dragon" +}; + +#define DS3_WORDS 322 +#define DS3_WORDS2 211 +const char words3[DS3_WORDS][32] = +{ + "enemy", + "monster", + "mob enemy", + "tough enemy", + "critical foe", + "Hollow", + "pilgrim", + "prisoner", + "monstrosity", + "skeleton", + "ghost", + "beast", + "lizard", + "bug", + "grub", + "crab", + "dwarf", + "giant", + "demon", + "dragon", + "knight", + "sellsword", + "warrior", + "herald", + "bandit", + "assassin", + "sorcerer", + "pyromancer", + "cleric", + "deprived", + "sniper", + "duo", + "trio", + "you", + "you bastard", + "good fellow", + "saint", + "wretch", + "charmer", + "poor soul", + "oddball", + "nimble one", + "laggard", + "moneybags", + "beggar", + "miscreant", + "liar", + "fatty", + "beanpole", + "youth", + "elder", + "old codger", + "old dear", + "miscreant", + "artisan", + "master", + "sage", + "champion", + "Lord of Cinder", + "king", + "queen", + "prince", + "princess", + "angel", + "god", + "friend", + "ally", + "spouse", + "covenantor", + "Phantom", + "Dark Spirit", + "bonfire", + "ember", + "fog wall", + "lever", + "contraption", + "key", + "trap", + "torch", + "door", + "treasure", + "chest", + "something", + "quite something", + "rubbish", + "filth", + "weapon", + "shield", + "projectile", + "armor", + "item", + "ring", + "ore", + "coal", + "transposing kiln", + "scroll", + "umbral ash", + "throne", + "rite", + "coffin", + "cinder", + "ash", + "moon", + "eye", + "brew", + "soup", + "message", + "bloodstain", + "illusion", + "close-ranged battle", + "ranged battle", + "eliminating one at a time", + "luring it out", + "beating to a pulp", + "ambush", + "pincer attack", + "hitting them in one swoop", + "dual-wielding", + "stealth", + "mimicry", + "fleeing", + "charging", + "jumping off", + "dashing through", + "circling around", + "trapping inside", + "rescue", + "Skill", + "sorcery", + "pyromancy", + "miracles", + "pure luck", + "prudence", + "brief respite", + "play dead", + "jog", + "dash", + "rolling", + "backstepping", + "jumping", + "attacking", + "jump attack", + "dash attack", + "counter attack", + "stabbing in the back", + "guard stun & stab", + "plunging attack", + "sweeping attack", + "shield breaking", + "blocking", + "parrying", + "locking-on", + "no lock-on", + "two-handing", + "gesture", + "control", + "destroy", + "boulder", + "lava", + "poison gas", + "enemy horde", + "forest", + "swamp", + "cave", + "shortcut", + "detour", + "hidden path", + "secret passage", + "dead end", + "labyrinth", + "hole", + "bright spot", + "dark spot", + "open area", + "tight spot", + "safe zone", + "danger zone", + "sniper spot", + "hiding place", + "illusory wall", + "ladder", + "lift", + "gorgeous view", + "looking away", + "overconfidence", + "slip-up", + "oversight", + "fatigue", + "bad luck", + "inattention", + "loss of stamina", + "chance encounter", + "planned encounter", + "front", + "back", + "left", + "right", + "up", + "down", + "below", + "above", + "behind", + "head", + "neck", + "stomach", + "back", + "arm", + "finger", + "leg", + "rear", + "tail", + "wings", + "anywhere", + "tongue", + "right arm", + "left arm", + "thumb", + "indexfinger", + "longfinger", + "ringfinger", + "smallfinger", + "right leg", + "left leg", + "right side", + "left side", + "pincer", + "wheel", + "core", + "mount", + "regular", + "strike", + "thrust", + "slash", + "magic", + "crystal", + "fire", + "chaos", + "lightning", + "blessing", + "dark", + "critical hits", + "bleeding", + "poison", + "toxic", + "frost", + "curse", + "equipment breakage", + "chance", + "quagmire", + "hint", + "secret", + "sleeptalk", + "happiness", + "misfortune", + "life", + "death", + "demise", + "joy", + "fury", + "agony", + "sadness", + "tears", + "loyalty", + "betrayal", + "hope", + "despair", + "fear", + "losing sanity", + "victory", + "defeat", + "sacrifice", + "light", + "dark", + "bravery", + "confidence", + "vigor", + "revenge", + "resignation", + "overwhelming", + "regret", + "pointless", + "man", + "woman", + "friendship", + "love", + "recklessness", + "composure", + "guts", + "comfort", + "silence", + "deep", + "good luck", + "fine work", + "I did it!", + "I've failed...", + "here!", + "not here!", + "I can't take this...", + "lonely...", + "don't you dare!", + "do it!", + "look carefully", + "listen carefully", + "think carefully", + "this place again?", + "now the real fight begins", + "you don't deserve this", + "keep moving", + "pull back", + "give it up", + "don't give up", + "help me...", + "impossible...", + "bloody expensive...", + "let me out of here...", + "stay calm", + "like a dream...", + "seems familiar...", + "are you ready?", + "it'll happen to you too", + "praise the Sun!", + "may the flames guide thee" +}; diff --git a/soapstone_er.h b/soapstone_er.h new file mode 100644 index 0000000..25e0b35 --- /dev/null +++ b/soapstone_er.h @@ -0,0 +1 @@ +// TODO Elden Ring messages diff --git a/ucextract.c b/ucextract.c new file mode 100644 index 0000000..b9b5810 --- /dev/null +++ b/ucextract.c @@ -0,0 +1,402 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UPKG_MAGIC 0x9E2A83C1 + +// uncomment if you want full data dumps, helpful if you need to reverse engineer some unsupported format +//#define _DEBUG 1 + +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; +} + +uint16_t readword( void ) +{ + uint16_t val = *(uint16_t*)(pkgfile+fpos); + fpos += 2; + return val; +} + +uint32_t readdword( void ) +{ + uint32_t val = *(uint32_t*)(pkgfile+fpos); + fpos += 4; + return val; +} + +float readfloat( void ) +{ + float val = *(float*)(pkgfile+fpos); + fpos += 4; + return val; +} + +#define READSTRUCT(x,y) {memcpy(&x,pkgfile+fpos,sizeof(y));fpos+=sizeof(y);} + +// 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 ( uint32_t 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 >= 55 ) 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; +} + +// fully read import table entry +void readimport2( int32_t *cpkg, int32_t *cname, int32_t *pkg, int32_t *name ) +{ + *cpkg = readindex(); + *cname = readindex(); + if ( head->pkgver >= 55 ) *pkg = readdword(); + else *pkg = 0; + *name = readindex(); +} + +void getimport2( int index, int32_t *cpkg, int32_t *cname, int32_t *pkg, + int32_t *name ) +{ + size_t prev = fpos; + fpos = head->oimports; + for ( int i=0; i<=index; i++ ) + readimport2(cpkg,cname,pkg,name); + fpos = prev; +} + +// read export table entry +void readexport( int32_t *class, int32_t *ofs, int32_t *siz, int32_t *name ) +{ + *class = readindex(); + readindex(); + if ( head->pkgver >= 55 ) fpos += 4; + *name = readindex(); + fpos += 4; + *siz = readindex(); + if ( *siz > 0 ) *ofs = readindex(); +} + +void getexport( int index, int32_t *class, int32_t *ofs, int32_t *siz, + int32_t *name ) +{ + size_t prev = fpos; + fpos = head->oexports; + for ( int i=0; i<=index; i++ ) + readexport(class,ofs,siz,name); + fpos = prev; +} + +// fully read export table entry +void readexport2( int32_t *class, int32_t *super, int32_t *pkg, int32_t *name, + uint32_t *flags, int32_t *siz, int32_t *ofs ) +{ + *class = readindex(); + *super = readindex(); + if ( head->pkgver >= 55 ) *pkg = readdword(); + else *pkg = 0; + *name = readindex(); + *flags = readdword(); + *siz = readindex(); + if ( *siz > 0 ) *ofs = readindex(); +} + +void getexport2( int index, int32_t *class, int32_t *super, int32_t *pkg, + int32_t *name, uint32_t *flags, int32_t *siz, int32_t *ofs ) +{ + size_t prev = fpos; + fpos = head->oexports; + for ( int i=0; i<=index; i++ ) + readexport2(class,super,pkg,name,flags,siz,ofs); + fpos = prev; +} + +void savetext( int32_t namelen, char *name ) +{ + // TODO +} + +int main( int argc, char **argv ) +{ + if ( argc < 2 ) + { + printf("Usage: ucextract \n"); + 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("TextBuffer") ) + { + printf("Package %s does not contain script sources\n",argv[1]); + free(pkgfile); + return 4; + } + // loop through exports and search for sounds + fpos = head->oexports; + for ( uint32_t i=0; inexports; i++ ) + { + int32_t class, super, pkg, name, flags, siz, ofs; + readexport2(&class,&super,&pkg,&name,&flags,&siz,&ofs); + if ( (siz <= 0) || (class >= 0) ) continue; + // get the class name + class = -class-1; + if ( (uint32_t)class > head->nimports ) continue; + int32_t l = 0; + char *n = (char*)(pkgfile+getname(getimport(class),&l)); + int ismesh = !strncmp(n,"TextBuffer",l); + if ( !ismesh ) continue; + mkdir("Scripts",0775); + // get the highest group name (must be an export) + char *pkgn = 0; + int32_t pkgl = 0; + while ( pkg > 0 ) + { + int32_t pclass, psuper, ppkg, pname, pflags, psiz, pofs; + getexport2(pkg-1,&pclass,&psuper,&ppkg,&pname,&pflags,&psiz,&pofs); + pkgn = (char*)(pkgfile+getname(pname,&pkgl)); + pkg = ppkg; + } + char *snd = (char*)(pkgfile+getname(name,&l)); + if ( pkgn && strncmp(pkgn,"None",pkgl) ) + printf("Script found: %.*s.%.*s\n",pkgl,pkgn,l,snd); + else printf("Script found: %.*s\n",l,snd); + int32_t sndl = l; +#ifdef _DEBUG + char fname[256] = {0}; + snprintf(fname,256,"%.*s.object",sndl,snd); + printf(" Dumping full object data to %s\n",fname); + FILE *f = fopen(fname,"wb"); + fwrite(pkgfile+ofs,siz,1,f); + fclose(f); + continue; +#endif + // begin reading data + size_t prev = fpos; + fpos = ofs; + if ( head->pkgver < 45 ) fpos += 4; + if ( head->pkgver < 55 ) fpos += 16; + if ( head->pkgver <= 44 ) fpos -= 6; // ??? + if ( head->pkgver == 45 ) fpos -= 2; // ??? + if ( head->pkgver == 41 ) fpos += 2; // ??? + if ( head->pkgver <= 35 ) fpos += 8; // ??? + // process properties + int32_t prop = readindex(); + if ( (uint32_t)prop >= head->nnames ) + { + printf("Unknown property %d, skipping\n",prop); + fpos = prev; + continue; + } + char *pname = (char*)(pkgfile+getname(prop,&l)); +retry: + if ( strncasecmp(pname,"None",l) ) + { + uint8_t info = readbyte(); + int array = info&0x80; + int type = info&0xf; + int psiz = (info>>4)&0x7; + int32_t tl = 0; + char *sname; + if ( type == 10 ) + { + int32_t sn; + sn = readindex(); + sname = (char*)(pkgfile+getname(sn,&tl)); + } + switch ( psiz ) + { + case 0: + psiz = 1; + break; + case 1: + psiz = 2; + break; + case 2: + psiz = 4; + break; + case 3: + psiz = 12; + break; + case 4: + psiz = 16; + break; + case 5: + psiz = readbyte(); + break; + case 6: + psiz = readword(); + break; + case 7: + psiz = readdword(); + break; + } + //printf(" prop %.*s (%u, %u, %u, %u)\n",l,pname,array,type,(info>>4)&7,psiz); + //if ( tl ) printf(" struct: %.*s\n",tl,sname); + if ( array && (type != 3) ) + { + int idx = readindex(); + //printf(" index: %d\n",idx); + } + fpos += psiz; + prop = readindex(); + pname = (char*)(pkgfile+getname(prop,&l)); + goto retry; + } + savetext(sndl,snd,head->pkgver,pkgl,pkgn); + fpos = prev; + } + free(pkgfile); + return 0; +} diff --git a/uftxextract.c b/uftxextract.c new file mode 100644 index 0000000..2ee134b --- /dev/null +++ b/uftxextract.c @@ -0,0 +1,620 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UPKG_MAGIC 0x9E2A83C1 + +// uncomment if you want full data dumps, helpful if you need to reverse engineer some unsupported format +//#define _DEBUG 1 + +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; +} + +uint16_t readword( void ) +{ + uint16_t val = *(uint16_t*)(pkgfile+fpos); + fpos += 2; + return val; +} + +uint32_t readdword( void ) +{ + uint32_t val = *(uint32_t*)(pkgfile+fpos); + fpos += 4; + return val; +} + +float readfloat( void ) +{ + float val = *(float*)(pkgfile+fpos); + fpos += 4; + return val; +} + +#define READSTRUCT(x,y) {memcpy(&x,pkgfile+fpos,sizeof(y));fpos+=sizeof(y);} + +// 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 ( uint32_t 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 >= 55 ) 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; +} + +// fully read import table entry +void readimport2( int32_t *cpkg, int32_t *cname, int32_t *pkg, int32_t *name ) +{ + *cpkg = readindex(); + *cname = readindex(); + if ( head->pkgver >= 55 ) *pkg = readdword(); + else *pkg = readindex(); + *name = readindex(); +} + +void getimport2( int index, int32_t *cpkg, int32_t *cname, int32_t *pkg, + int32_t *name ) +{ + size_t prev = fpos; + fpos = head->oimports; + for ( int i=0; i<=index; i++ ) + readimport2(cpkg,cname,pkg,name); + fpos = prev; +} + +// read export table entry +void readexport( int32_t *class, int32_t *ofs, int32_t *siz, int32_t *name ) +{ + *class = readindex(); + readindex(); + if ( head->pkgver >= 55 ) fpos += 4; + *name = readindex(); + fpos += 4; + *siz = readindex(); + if ( *siz > 0 ) *ofs = readindex(); +} + +void getexport( int index, int32_t *class, int32_t *ofs, int32_t *siz, + int32_t *name ) +{ + size_t prev = fpos; + fpos = head->oexports; + for ( int i=0; i<=index; i++ ) + readexport(class,ofs,siz,name); + fpos = prev; +} + +// fully read export table entry +void readexport2( int32_t *class, int32_t *super, int32_t *pkg, int32_t *name, + uint32_t *flags, int32_t *siz, int32_t *ofs ) +{ + *class = readindex(); + *super = readindex(); + if ( head->pkgver >= 55 ) *pkg = readdword(); + else *pkg = 0; + *name = readindex(); + *flags = readdword(); + *siz = readindex(); + if ( *siz > 0 ) *ofs = readindex(); +} + +void getexport2( int index, int32_t *class, int32_t *super, int32_t *pkg, + int32_t *name, uint32_t *flags, int32_t *siz, int32_t *ofs ) +{ + size_t prev = fpos; + fpos = head->oexports; + for ( int i=0; i<=index; i++ ) + readexport2(class,super,pkg,name,flags,siz,ofs); + fpos = prev; +} + +typedef struct +{ + uint8_t r, g, b, x; +} __attribute__((packed)) color_t; + +typedef struct +{ + uint8_t r, g, b; +} __attribute__((packed)) pal8_t; + +const char sparktypes[29][32] = +{ + "Burn", "Sparkle", "Pulse", "Signal", "Blaze", + "OzHasSpoken", "Cone", "BlazeRight", "BlazeLeft", + "Cylinder", "Cylinder3D", "Lissajous", "Jugglers", + "Emit", "Fountain", "Flocks", "Eels", "Organic", + "WanderOrganic", "RandomCloud", "CustomCloud", + "LocalCloud", "Stars", "LineLighting", "RampLighting", + "SphereLighting", "Wheel", "Gametes", "Sprinkler" +}; + +void savepalette( uint32_t pal, int32_t *palnamelen, char **palname ) +{ + size_t prev = fpos; + int32_t class, ofs, siz, name; + getexport(pal-1,&class,&ofs,&siz,&name); + *palname = (char*)(pkgfile+getname(name,palnamelen)); + // begin reading data + fpos = ofs; + if ( head->pkgver < 45 ) fpos += 4; + if ( head->pkgver < 55 ) fpos += 16; + if ( head->pkgver <= 44 ) fpos -= 6; // ??? + if ( head->pkgver == 45 ) fpos -= 2; // ??? + if ( head->pkgver <= 35 ) fpos += 8; // ??? + // process properties + int32_t prop = readindex(); + if ( (uint32_t)prop >= head->nnames ) + { + printf("Unknown property %d, skipping\n",prop); + fpos = prev; + return; + } + int32_t l = 0; + char *pname = (char*)(pkgfile+getname(prop,&l)); +retrypal: + if ( strncasecmp(pname,"none",l) ) + { + uint8_t info = readbyte(); + //int array = info&0x80; + int type = info&0xf; + int psiz = (info>>4)&0x7; + switch ( psiz ) + { + case 0: + psiz = 1; + break; + case 1: + psiz = 2; + break; + case 2: + psiz = 4; + break; + case 3: + psiz = 12; + break; + case 4: + psiz = 16; + break; + case 5: + psiz = readbyte(); + break; + case 6: + psiz = readword(); + break; + case 7: + psiz = readdword(); + break; + } + if ( type == 10 ) + readindex(); // skip struct name + fpos += psiz; + prop = readindex(); + pname = (char*)(pkgfile+getname(prop,&l)); + goto retrypal; + } + int32_t grouplen = 0; + char *group = 0; + if ( head->pkgver < 55 ) + { + // group + fpos++; + int32_t grp = readindex(); + group = (char*)(pkgfile+getname(grp,&grouplen)); + } + // number of colors + int32_t num = readindex(); + char fname[256]; + if ( group && strncmp(group,"None",grouplen) ) + { + snprintf(fname,256,"Palettes/%.*s",grouplen,group); + mkdir(fname,0775); + snprintf(fname,256,"Palettes/%.*s/%.*s.pal",grouplen,group, + *palnamelen,*palname); + } + else snprintf(fname,256,"Palettes/%.*s.pal",*palnamelen,*palname); + color_t *paldat = (color_t*)(pkgfile+fpos); + pal8_t *palconv = calloc(sizeof(pal8_t),num); + for ( int i=0; i= 63 ) ofs = readdword(); + uint32_t datasiz = readindex(); + if ( version >= 63 ) fpos = ofs; + else fpos += datasiz; + uint32_t w = readdword(); + uint32_t h = readdword(); + readbyte(); // ubits + readbyte(); // vbits + // skip all the other mipmaps, we only need the dimensions of mip zero + for ( int i=1; i= 63 ) ofs = readdword(); + datasiz = readindex(); + if ( version >= 63 ) fpos = ofs; + else fpos += datasiz; + readdword(); + readdword(); + readbyte(); + readbyte(); + } + char fname[256]; + if ( group && strncmp(group,"None",grouplen) ) + { + snprintf(fname,256,"Textures/%.*s",grouplen,group); + mkdir(fname,0775); + snprintf(fname,256,"Textures/%.*s/%.*s.ftx",grouplen,group, + namelen,name); + } + else snprintf(fname,256,"Textures/%.*s.ftx",namelen,name); + // we're using stdio for this one, it's cheaper + FILE *f = fopen(fname,"wb"); + if ( !f ) + { + printf("Failed to open file %s: %s\n",fname,strerror(errno)); + return; + } + // texture name, width and height + fprintf(f,"%.*s %u %u\n",namelen,name,w,h); + // palette name + int32_t palnamelen = 0; + char *palname = 0; + savepalette(pal,&palnamelen,&palname); + fprintf(f,"%.*s\n",palnamelen,palname); + // bRising, RenderHeat and SparkLimit + fprintf(f,"%d %hhu %u\n",!!brising,renderheat,sparklimit); + // individual sparks (type as string, heat, x, y, args[4]) + int32_t nsparks = readindex(); // TArray size + for ( int32_t i=0; i= 29 ) continue; // discard unknown types + fprintf(f,"%s %hhu %hhu %hhu %hhu %hhu %hhu %hhu\n", + sparktypes[type],heat,x,y, + args[0],args[1],args[2],args[3]); + } + fclose(f); +} + +int main( int argc, char **argv ) +{ + if ( argc < 2 ) + { + printf("Usage: uftxextract \n"); + 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; + } + // loop through exports and search for textures + fpos = head->oexports; + for ( uint32_t i=0; inexports; i++ ) + { + int32_t class, super, pkg, name, siz, ofs; + uint32_t flags; + readexport2(&class,&super,&pkg,&name,&flags,&siz,&ofs); + if ( (siz <= 0) || (class >= 0) ) continue; + // get the class name + class = -class-1; + if ( (uint32_t)class > head->nimports ) continue; + int32_t l = 0; + char *n = (char*)(pkgfile+getname(getimport(class),&l)); + int istex = !strncasecmp(n,"FireTexture",l); + if ( !istex ) continue; + mkdir("Textures",0775); + mkdir("Palettes",0775); + // get the highest group name (must be an export) + char *pkgn = 0; + int32_t pkgl = 0; + while ( pkg > 0 ) + { + int32_t pclass, psuper, ppkg, pname, psiz, pofs; + uint32_t pflags; + getexport2(pkg-1,&pclass,&psuper,&ppkg,&pname,&pflags,&psiz,&pofs); + pkgn = (char*)(pkgfile+getname(pname,&pkgl)); + pkg = ppkg; + } + char *tex = (char*)(pkgfile+getname(name,&l)); + if ( pkgn && strncmp(pkgn,"None",pkgl) ) + printf("FireTexture found: %.*s.%.*s\n",pkgl,pkgn,l,tex); + else printf("FireTexture found: %.*s\n",l,tex); + int32_t texl = l; +#ifdef _DEBUG + char fname[256] = {0}; + snprintf(fname,256,"%.*s.object",texl,tex); + printf(" Dumping full object data to %s\n",fname); + FILE *f = fopen(fname,"wb"); + fwrite(pkgfile+ofs,siz,1,f); + fclose(f); + continue; +#endif + // begin reading data + size_t prev = fpos; + fpos = ofs; + if ( head->pkgver < 45 ) fpos += 4; + if ( head->pkgver < 55 ) fpos += 16; + if ( head->pkgver <= 44 ) fpos -= 6; // ??? + if ( head->pkgver == 45 ) fpos -= 2; // ??? + if ( head->pkgver == 41 ) fpos += 2; // ??? + if ( head->pkgver <= 35 ) fpos += 8; // ??? + // process properties + int32_t prop = readindex(); + if ( (uint32_t)prop >= head->nnames ) + { + printf("Unknown property %d, skipping\n",prop); + fpos = prev; + continue; + } + int32_t pal = 0; // we need to fetch this + char *pname = (char*)(pkgfile+getname(prop,&l)); + uint8_t rising = 0; // as well as these + uint8_t heat = 200; + uint32_t limit = 1024; +retry: + if ( strncasecmp(pname,"None",l) ) + { + uint8_t info = readbyte(); + int array = info&0x80; + int type = info&0xf; + int psiz = (info>>4)&0x7; + //int32_t tl = 0; + //char *sname = 0; + if ( type == 10 ) + { + //int32_t sn; + /*sn = */readindex(); + //sname = (char*)(pkgfile+getname(sn,&tl)); + } + switch ( psiz ) + { + case 0: + psiz = 1; + break; + case 1: + psiz = 2; + break; + case 2: + psiz = 4; + break; + case 3: + psiz = 12; + break; + case 4: + psiz = 16; + break; + case 5: + psiz = readbyte(); + break; + case 6: + psiz = readword(); + break; + case 7: + psiz = readdword(); + break; + } + //printf(" prop %.*s (%u, %u, %u, %u)\n",l,pname,array,type,(info>>4)&7,psiz); + //if ( tl ) printf(" struct: %.*s\n",tl,sname); + if ( array && (type != 3) ) + { + /*int idx = */readindex(); + //printf(" index: %d\n",idx); + } + if ( !strncasecmp(pname,"Palette",l) ) + pal = readindex(); + else if ( !strncasecmp(pname,"bRising",l) ) + rising = (info>>7)&1; + else if ( !strncasecmp(pname,"RenderHeat",l) ) + heat = readbyte(); + else if ( !strncasecmp(pname,"SparksLimit",l) ) + limit = readdword(); + else fpos += psiz; + prop = readindex(); + pname = (char*)(pkgfile+getname(prop,&l)); + goto retry; + } + if ( pal ) + savetexture(texl,tex,pal,rising,heat,limit,head->pkgver,pkgl,pkgn); + fpos = prev; + } + free(pkgfile); + return 0; +} diff --git a/umodextract.c b/umodextract.c index c5e0278..996e88b 100644 --- a/umodextract.c +++ b/umodextract.c @@ -3,6 +3,7 @@ #include #include #include +#include #define UMOD_MAGIC 0x9FE3C5A3 @@ -94,6 +95,8 @@ int main( int argc, char **argv ) int32_t ndir = readindex(f); //printf("ndir: %d\n",ndir); umoddir_t *dir = calloc(ndir,sizeof(umoddir_t)); + char cwd[4096]; + getcwd(&cwd[0],4096); for ( int32_t i=0; i