diff --git a/README.md b/README.md index a132a56..b046aa1 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,22 @@ 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. +* cube2enviro: A simple GL 4.4 program. Loads a cubemap and draws a flattened + hemisphere environment map that can be used in Unreal. * ddsinfo: Shows contents of a DDS header. * 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. * fmod\_playbank (formerly fuck\_fmod): Tool for playback of .fsb files. * fuzz: A fancy blocky noise filter using proto-AliceGL designs. * glfuzz: OpenGL version of the filter. +* 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. + 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. * mkfont: A tool I use to convert UE fonts exported with UTPT into fonts for @@ -22,6 +31,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. +* pcxpalex.c: Extracts the palette from PCX images. * pframes: Short utility for automating long FrameIndex lists for MODELDEF. * schange: Program used along with mkwall to update the wallpaper on screen geometry changes. @@ -30,11 +40,21 @@ 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. +* u95/u083/u086extract: Programs for extracting data from Unreal alpha + packages. This and other Unreal tools might be shifted to another repo. * 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. * umxunpack: Extractor for music in UE 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. * 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. * withhands: Talk like W.D. Gaster. * zfs-rootfs.patch: The original patch for archzfs to support my specific rootfs dataset mountpoint quirks. diff --git a/cube2enviro.c b/cube2enviro.c new file mode 100644 index 0000000..f00ebd8 --- /dev/null +++ b/cube2enviro.c @@ -0,0 +1,220 @@ +#include +#include +#include +#include + +const char *vsrc = +"#version 430\n" +"layout(location=0) in vec3 vPosition;\n" +"layout(location=1) in vec2 vCoord;\n" +"out vec2 fCoord;\n" +"void main()\n" +"{\n" +"\tgl_Position.xyz = vPosition;\n" +"\tgl_Position.w = 1.0;\n" +"\tfCoord = vCoord;\n" +"}\n"; +const char *fsrc = +"#version 430\n" +"in vec2 fCoord;\n" +"layout(location=0) out vec4 FragColor;\n" +"layout(binding=0) uniform samplerCube Texture;\n" +"void main()\n" +"{\n" +"\tvec2 p = fCoord-0.5;\n" +"\tfloat pitch = length(p)*3.14159265;\n" +"\tfloat roll = atan(p.y,p.x);\n" +"\tvec3 ccoord = vec3(sin(pitch)*cos(roll),-cos(pitch),sin(pitch)*sin(roll));\n" +"\tvec4 res = texture(Texture,-ccoord);\n" +"\tFragColor = res;\n" +"}\n"; + +GLint prog; +GLuint vao, vbuf, tex; + +int tex_load( const char *basename ) +{ + const char f[6] = {'b','f','l','r','d','t'}; + const int glf[6] = + { + GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, + GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + GL_TEXTURE_CUBE_MAP_POSITIVE_X, + GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, + }; + char filename[256]; + glGenTextures(1,&tex); + glBindTexture(GL_TEXTURE_CUBE_MAP,tex); + glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_MIN_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_R,GL_CLAMP_TO_EDGE); + for ( int i=0; i<6; i++ ) + { + snprintf(filename,256,"%s_%c.png",basename,f[i]); + SDL_Surface *tx = IMG_Load(filename); + if ( !tx ) + { + fprintf(stderr,"could not load %s: %s\n",filename,SDL_GetError()); + return -1; + } + SDL_Surface *txconv = SDL_ConvertSurfaceFormat(tx,SDL_PIXELFORMAT_RGBA32,0); + glTexImage2D(glf[i],0,GL_RGBA,txconv->w,txconv->h,0,GL_RGBA,GL_UNSIGNED_BYTE,txconv->pixels); + SDL_FreeSurface(txconv); + SDL_FreeSurface(tx); + } + return 0; +} + +void init_render() +{ + glClearColor(0.5f,0.5f,0.5f,1.f); + glClearDepth(1.f); + glUseProgram(prog); + glBindBuffer(GL_ARRAY_BUFFER,vbuf); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glBindTexture(GL_TEXTURE_CUBE_MAP,tex); + glActiveTexture(GL_TEXTURE0); +} + +void draw_frame() +{ + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,sizeof(float)*5, + (char*)(0)); + glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,sizeof(float)*5, + (char*)(sizeof(float)*3)); + glDrawArrays(GL_TRIANGLE_STRIP,0,4); +} + +void scr_load() +{ + glGenVertexArrays(1,&vao); + glBindVertexArray(vao); + glGenBuffers(1,&vbuf); + glBindBuffer(GL_ARRAY_BUFFER,vbuf); + float vboe[20] = + { + -1, -1, 0, 0., 0., + -1, 1, 0, 0., 1., + 1, -1, 0, 1., 0., + 1, 1, 0, 1., 1., + }; + glBufferData(GL_ARRAY_BUFFER,sizeof(float)*20,&vboe[0], + GL_STATIC_DRAW); +} + +GLint compile_shader( GLenum type, const char *src ) +{ + GLint hnd, len, suc; + char *log; + hnd = glCreateShader(type); + len = strlen(src); + glShaderSource(hnd,1,&src,&len); + glCompileShader(hnd); + glGetShaderiv(hnd,GL_COMPILE_STATUS,&suc); + if ( !suc ) + { + glGetShaderiv(hnd,GL_INFO_LOG_LENGTH,&len); + log = malloc(len); + glGetShaderInfoLog(hnd,len,&suc,log); + fprintf(stderr,"Shader compile error:\n%s\n",log); + free(log); + return -1; + } + return hnd; +} + +GLint link_shader( GLint geom, GLint vert, GLint frag ) +{ + GLint hnd, suc, len; + char *log; + hnd = glCreateProgram(); + if ( geom != -1 ) glAttachShader(hnd,geom); + if ( vert != -1 ) glAttachShader(hnd,vert); + if ( frag != -1 ) glAttachShader(hnd,frag); + glLinkProgram(hnd); + glGetProgramiv(hnd,GL_LINK_STATUS,&suc); + if ( !suc ) + { + glGetShaderiv(hnd,GL_INFO_LOG_LENGTH,&len); + log = malloc(len); + glGetShaderInfoLog(hnd,len,&suc,log); + fprintf(stderr,"Shader link error:\n%s\n",log); + free(log); + return -1; + } + return hnd; +} + +int prog_load() +{ + GLint vert, frag; + if ( (frag=compile_shader(GL_FRAGMENT_SHADER,fsrc)) == -1 ) return -1; + if ( (vert=compile_shader(GL_VERTEX_SHADER,vsrc)) == -1 ) return -1; + if ( (prog=link_shader(-1,vert,frag)) == -1 ) return -1; + glDeleteShader(frag); + glDeleteShader(vert); + return 0; +} + +int main( int argc, char **argv ) +{ + int retcode = 0; + if ( argc < 2 ) + { + fprintf(stderr,"usage: cube2enviro [size]\n"); + return 0; + } + int sz = 256; + if ( argc > 2 ) sscanf(argv[2],"%u",&sz); + SDL_Init(SDL_INIT_VIDEO|SDL_INIT_EVENTS); + IMG_Init(IMG_INIT_PNG); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,3); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,4); + SDL_Window *win = SDL_CreateWindow("cube2enviro", + SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,sz,sz, + SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN); + SDL_GLContext *ctx = SDL_GL_CreateContext(win); + SDL_GL_SetSwapInterval(1); + if ( tex_load(argv[1]) == -1 ) + { + retcode = 1; + goto bail; + } + if ( prog_load() == -1 ) + { + retcode = 2; + goto bail; + } + scr_load(); + init_render(); + SDL_Event e; + int active = 1; + float frame = 0.f; + long tick, tock; + while ( active ) + { + while ( SDL_PollEvent(&e) ) + { + if ( (e.type == SDL_QUIT) || (((e.type == SDL_KEYDOWN) + || (e.type == SDL_KEYUP)) + && (e.key.keysym.sym == SDLK_ESCAPE)) ) + active = 0; + } + draw_frame(); + SDL_GL_SwapWindow(win); + } +bail: + SDL_GL_DeleteContext(ctx); + SDL_DestroyWindow(win); + IMG_Quit(); + SDL_Quit(); + return retcode; +} + diff --git a/ddsinfo.c b/ddsinfo.c index 5866d5a..f0d6b84 100644 --- a/ddsinfo.c +++ b/ddsinfo.c @@ -266,6 +266,10 @@ int main( int argc, char **argv ) if ( head.caps[1]&DDSCAPS2_CUBEMAP_POSITIVEZ ) printf(" POSITIVEZ"); if ( head.caps[1]&DDSCAPS2_CUBEMAP_NEGATIVEZ ) printf(" NEGATIVEZ"); if ( head.caps[1]&DDSCAPS2_VOLUME ) printf(" VOLUME"); + if ( !strncmp(head.pf_fourcc,"DX10",4) ) + { + printf("\ndxgi format: %d",head10.dxgiformat); + } printf("\n"); return 0; } diff --git a/dood.c b/dood.c index d581a99..7608d54 100644 --- a/dood.c +++ b/dood.c @@ -1,30 +1,56 @@ #include +#include +#include -unsigned short endoom[80*25]; - -int val( unsigned short ch ) +int nspace( unsigned short ch ) { return ((ch>0x2F)&&(ch<0x3A))||((ch>0x40)&&(ch<0x5B)) ||((ch>0x60)&&(ch<0x7B)); } -int main( void ) +int main( int argc, char **argv ) { - fread(endoom,sizeof(short),80*25,stdin); + if ( argc < 3 ) + { + fprintf(stderr,"usage: dood \n"); + return 0; + } + unsigned short endoom[2000]; + FILE *fin = fopen(argv[1],"rb"); + if ( !fin ) + { + fprintf(stderr,"failed to open input: %s.\n",strerror(errno)); + return 1; + } + int nch = 0; + if ( (nch = fread(endoom,sizeof(short),2000,fin)) < 2000 ) + { + fprintf(stderr,"short read, expected 2000 chars got %d.\n",nch); + fclose(fin); + return 2; + } + fclose(fin); int c = 0; do { - if ( val(endoom[c]&0xFF) ) + if ( nspace(endoom[c]&0xFF) ) { int s = c; - while ( (c++ < 80*25 ) && val(endoom[c]&0xFF) ); + while ( (c++ < 2000 ) && nspace(endoom[c]&0xFF) ); int e = c; for ( int i=0; i<(e-s)/2; i++ ) endoom[e-i-1] = endoom[s+i]; } else c++; } - while ( c < 80*25 ); - fwrite(endoom,sizeof(short),80*25,stdout); + while ( c < 2000 ); + FILE *fout = fopen(argv[2],"wb"); + if ( !fout ) + { + fprintf(stderr,"failed to open output: %s.\n",strerror(errno)); + return 4; + } + fwrite(endoom,sizeof(short),2000,fout); + close(fout); return 0; } diff --git a/mazestuff.c b/mazestuff.c new file mode 100644 index 0000000..a491713 --- /dev/null +++ b/mazestuff.c @@ -0,0 +1,522 @@ +/* + A C implementation of some dungeon generator, or at least an attempt. + Based mainly on http://journal.stuffwithstuff.com/2014/12/21/rooms-and-mazes + (C)2019 Marisa Kirisame, UnSX Team. + Released under the LGPLv3. +*/ +#include +#include +#include +#include +#include + +// map dimensions +// should be odd, otherwise you'll get some "padding" on the bottom and right +#define MAP_WIDTH 32 +#define MAP_HEIGHT 32 +// dimensions of the display window +#define WIN_WIDTH (MAP_WIDTH*16) +#define WIN_HEIGHT (MAP_HEIGHT*16) +// size of each cell in pixels when rendered +#define CW (WIN_WIDTH/MAP_WIDTH) +#define CH (WIN_HEIGHT/MAP_HEIGHT) + +// delay after each "draw map" operation +// helps visualize the generation better +#define GENDELAY 10 + +// room generator parameters +// sizes must be odd +// more tries means more dense room placement +// I wonder what the probability of there being only one room could be +#define ROOM_MIN 3 +#define ROOM_MAX 7 +#define ROOM_TRIES 100 + +// tile types +#define TILE_WALL 0 +#define TILE_AIR 1 +#define TILE_DOOR 2 + +// probability of mazes being more squiggly +#define WINDING_PCT 0.35 +// probability of additional doors into a region appearing +#define EXTDOOR_PCT 0.15 +// probability of doors connecting back to the same region +#define SELFDOOR_PCT 0.25 + +// the map itself +uint8_t map[MAP_WIDTH][MAP_HEIGHT] = {{TILE_WALL}}; +// what region each cell is part of +// 0 = no region +int region[MAP_WIDTH][MAP_HEIGHT] = {{0}}; +// secondary region, for grouping rooms +int sregion[MAP_WIDTH][MAP_HEIGHT] = {{0}}; + +SDL_Surface *ws; +SDL_Window *w; + +int active = 1; + +// a 2d vector +typedef struct +{ + int x, y; +} vec2_t; + +// a room +typedef struct +{ + int x1, y1, x2, y2; +} room_t; + +// a connection between two regions +typedef struct +{ + int x, y, r[2]; +} conn_t; + +// do these rooms touch each other? +int room_intersect( room_t* a, room_t* b ) +{ + return ((a->x1 <= b->x2) && (a->x2 >= b->x1) + && (a->y1 <= b->y2) && (a->y2 >= b->y1)); +} + +vec2_t dirs[8] = +{ + { 0,-1}, // north + { 0, 1}, // south + {-1, 0}, // west + { 1, 0}, // east + {-1,-1}, // northwest + { 1,-1}, // northeast + {-1, 1}, // southwest + { 1, 1}, // southeast +}; + +int current_region = 0; +int current_sregion = 0; + +// RNG stuff +double FRandom( double a, double b ) +{ + return a + (rand()/(RAND_MAX/(b-a))); +} +int Random( int a, int b ) +{ + return FRandom(a,b); +} + +// draw the map surface onto the window surface +// black for walls, white for air +void drawmap( void ) +{ + uint32_t *px = ws->pixels; + uint32_t pal[3] = + { + SDL_MapRGB(ws->format,16,16,16), + SDL_MapRGB(ws->format,192,192,192), + SDL_MapRGB(ws->format,16,255,16), + }; + for ( int y=0; yy1; jy2; j++ ) for ( int i=r->x1; ix2; i++ ) + carve(i,j,TILE_AIR); + // add other stuff here if you want +} + +// place rooms +void add_rooms() +{ + int rw, rh, rx, ry; + room_t *rooms = malloc(0); + int nrooms = 0; + for ( int i=0; i= MAP_WIDTH) || (y < 0) || (y >= MAP_HEIGHT) ) + return 0; + x = sx+dirs[dir].x*2; + y = sy+dirs[dir].y*2; + return (map[x][y] == TILE_WALL); +} + +// make a maze +void grow_maze( int sx, int sy ) +{ + int lastdir = -1; + current_region++; + carve(sx,sy,TILE_AIR); + vec2_t *cells = malloc(sizeof(vec2_t)); + int ncells = 1; + cells[0].x = sx; + cells[0].y = sy; + while ( ncells > 0 ) + { + vec2_t *ccell = &cells[ncells-1]; + int carvedirs[4] = {0}; + int cancarve = 0; + for ( int i=0; i<4; i++ ) + { + if ( can_carve(ccell->x,ccell->y,i) ) + { + carvedirs[i] = 1; + cancarve = 1; + } + } + if ( cancarve ) + { + int dir; + if ( (lastdir != -1) && carvedirs[lastdir] + && (FRandom(0,1) > WINDING_PCT) ) + dir = lastdir; + else + { + // cheap-arse random picking + int wdir[4]; + int ndirs = 0; + int j = 0; + for ( int i=0; i<4; i++ ) + { + if ( !carvedirs[i] ) continue; + wdir[j++] = i; + ndirs++; + } + // shuffle + for ( int i=0; ix+dirs[dir].x; + int y = ccell->y+dirs[dir].y; + carve(x,y,TILE_AIR); + x += dirs[dir].x; + y += dirs[dir].y; + carve(x,y,TILE_AIR); + ncells++; + cells = realloc(cells,sizeof(vec2_t)*ncells); + cells[ncells-1].x = x; + cells[ncells-1].y = y; + lastdir = dir; + } + else + { + ncells--; + cells = realloc(cells,sizeof(vec2_t)*ncells); + lastdir = -1; + } + } + free(cells); +} + +// this generates doors +// it's kind of convoluted +// but not as much as the original implementation lol +void connect_regions() +{ + int creg[MAP_WIDTH][MAP_HEIGHT][5] = {{{0}}}; + for ( int j=1; j SELFDOOR_PCT) ) + continue; + nconn++; + conn = realloc(conn,nconn*sizeof(conn_t)); + memcpy(conn+nconn-1,&cconn,sizeof(conn_t)); + } + // iterate through all regions and place doors + // make sure there is at least ONE door between two regions + // but allow a random chance for more + for ( int i=1; i EXTDOOR_PCT ) continue; + // open an extra door + carve(conn[connl[j]].x,conn[connl[j]].y,TILE_DOOR); + } + free(connl); + } + // clean up any overlapping doors + // must be done on a second pass + for ( int i=0; i 0 ); +} + +// get rid of groups of rooms that are "isolated" from the larger part of the +// dungeon +int spread_sregion( int x, int y ) +{ + // non-recursive flood fill using the same queue system as the maze + // generator + int ssz = 1; + int ncells = 1; + vec2_t *cells = malloc(sizeof(vec2_t)); + sregion[x][y] = current_sregion; + cells[0].x = x; + cells[0].y = y; + while ( ncells > 0 ) + { + x = cells[ncells-1].x; + y = cells[ncells-1].y; + int dir = -1; + for ( int i=0; i<4; i++ ) + { + if ( (map[x+dirs[i].x][y+dirs[i].y] == TILE_WALL) + || sregion[x+dirs[i].x][y+dirs[i].y] ) + continue; + dir = i; + break; + } + if ( dir != -1 ) + { + ncells++; + cells = realloc(cells,sizeof(vec2_t)*ncells); + sregion[x+dirs[dir].x][y+dirs[dir].y] = current_sregion; + cells[ncells-1].x = x+dirs[dir].x; + cells[ncells-1].y = y+dirs[dir].y; + ssz++; + } + else + { + ncells--; + cells = realloc(cells,sizeof(vec2_t)*ncells); + } + } + free(cells); + return ssz; +} + +void clean_isolated() +{ + int *ssizes = malloc(0); + for ( int j=1; j ssizes[largest] ) largest = i; + for ( int j=1; j 1 ) sscanf(argv[1],"%u",&seed); + else seed = time(0); + srand(seed); + printf("seed: %u\n",seed); + SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_EVENTS); + w = SDL_CreateWindow("Dungeon",SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED,WIN_WIDTH,WIN_HEIGHT, + SDL_WINDOW_SHOWN); + ws = SDL_GetWindowSurface(w); + dungeon_make(); + while ( active ) drawmap(); + return 0; +} diff --git a/pcxpalex.c b/pcxpalex.c new file mode 100644 index 0000000..34ef9f7 --- /dev/null +++ b/pcxpalex.c @@ -0,0 +1,25 @@ +#include +#include + +int main( int argc, char **argv ) +{ + FILE *fin = fopen(argv[1],"rb"); + uint8_t head[4]; + fread(&head[0],4,1,fin); + if ( (head[0] != 0x0A) || (head[2] != 0x01) || (head[3] != 0x08) ) + { + fclose(fin); + return 1; + } + uint8_t pal[768]; + fseek(fin,-769,SEEK_END); + if ( fgetc(fin) != 0x0C ) + { + fclose(fin); + return 2; + } + fread(&pal[0],768,1,fin); + fclose(fin); + fwrite(&pal[0],768,1,stdout); + return 0; +} diff --git a/pframes.c b/pframes.c index d40b2cb..670eeeb 100644 --- a/pframes.c +++ b/pframes.c @@ -19,7 +19,7 @@ int main( int argc, char **argv ) printf("\tFrameIndex %.4s %c 0 %d\n",s,f,fi); fi+=sk; f++; - if ( f > 'Z' ) + if ( f > ']' ) { sp++; if ( sp == 2 ) diff --git a/u083extract.c b/u083extract.c new file mode 100644 index 0000000..54bdca3 --- /dev/null +++ b/u083extract.c @@ -0,0 +1,889 @@ +/* + extract text, meshes, textures, palettes and sounds from 0.82~0.84 + packages. + TODO: extract animseq and meshmap data for meshes +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NAMES_MARK "[REFERENCED NAMES]" +#define IMPORT_MARK "[IMPORTED RESOURCES]" +#define EXPORT_MARK "[EXPORTED RESOURCES]" +#define DATA_MARK "[EXPORTED RESOURCE DATA]" +#define HEADER_MARK "[EXPORTED RESOURCE HEADERS]" +#define TABLE_MARK "[EXPORTED RESOURCE TABLE]" +#define TRAILER_MARK "[SUMMARY]" +#define RES_FILE_TAG "Unreal Resource\x1A" + +#define RES_FILE_VERSION_083 (0x0009) +#define RES_FILE_VERSION_084 (0x000A) + +enum EResourceType +{ + RES_None = 0, // No resource. + RES_Buffer = 1, // A large binary object holding misc saveable data. + RES_Array = 3, // An array of resources. + RES_TextBuffer = 4, // A text buffer. + RES_Texture = 5, // Texture or compound texture. + RES_Font = 6, // Font for use in game. + RES_Palette = 7, // A palette. + RES_Script = 9, // Script. + RES_Class = 10, // An actor class. + RES_ActorList = 11, // An array of actors. + RES_Sound = 12, // Sound effect. + RES_Mesh = 14, // Animated mesh. + RES_Vectors = 16, // 32-bit floating point vector list. + RES_BspNodes = 17, // Bsp node list. + RES_BspSurfs = 18, // Bsp polygon list. + RES_LightMesh = 19, // Bsp polygon lighting mesh. + RES_Polys = 20, // Editor polygon list. + RES_Model = 21, // Model or level map. + RES_Level = 22, // A game level. + RES_Camera = 25, // A rendering camera on this machine. + RES_Player = 28, // A remote player logged into the local server. + RES_VertPool = 29, // A vertex pool corresponding to a Bsp and FPoints/FVectors table. + RES_Ambient = 30, // An ambient sound definition. + RES_TransBuffer = 31, // Transaction tracking buffer. + RES_MeshMap = 32, // MeshMap. + RES_Bounds = 33, // Bounding structure. + RES_Terrain = 34, // Terrain. + RES_Enum = 35, // Enumeration (array of FName's). +}; + +const char typenames[36][32] = +{ + "None", "Buffer", "Type2", "Array", "TextBuffer", "Texture", "Font", + "Palette", "Type8", "Script", "Class", "ActorList", "Sound", "Type13", + "Mesh", "Type15", "Vectors", "BspNodes", "BspSurfs", "LightMesh", + "Polys", "Model", "Level", "Type23", "Type24", "Camera", "Type26", + "Type27", "Player", "VertPool", "Ambient", "TransBuffer", "MeshMap", + "Bounds", "Terrain", "Enum" +}; + +typedef struct +{ + uint32_t version, nexports, nimports, nnames, onames, oimports, + oexports, oheaders, lheaders; + uint8_t pad[16]; + char deltafname[80]; + char tag[32]; +} resourcefiletrailer_t; + +typedef struct +{ + char name[32]; + uint32_t flags; +} nameentry_t; + +typedef struct +{ + char name[32]; + uint32_t version, type, size; +} resnamefileentry_t; + +typedef struct +{ + uint32_t lead; // unused leading data + uint32_t type; // should be equal to the export's type + char name[32]; // descriptive name of class + uint32_t dataptr; // empty, set at runtime by engine + uint32_t index, version, filenum, flags, dataofs, datasize, crc, unused; +} uresource_t; + +typedef struct +{ + uresource_t res; + int32_t num, max; +} udatabase_t; + +typedef struct +{ + udatabase_t db; +} ubuffer_t; + +typedef struct +{ + udatabase_t db; +} uarray_t; + +typedef struct +{ + udatabase_t db; + int32_t pos; +} utextbuffer_t; + +typedef struct +{ + uint8_t r, g, b, x; +} color_t; + +typedef struct +{ + uresource_t res; + uint32_t class, palette, microtexture, fireparams; + uint16_t familyname, unusedname; + float paldiffusionc, diffusec, specularc, frictionc; + uint32_t footstepsound, hitsound, polyflags, notile, lockcount, + cameracaps, pad3, pad4, pad5, pad6, pad7, pad8; + int32_t usize, vsize, datasize, colorbytes; + color_t mipzero; + uint32_t mipofs[8]; + uint8_t ubits, vbits, unused1, unused2, pad[32]; +} utexture083_t; + +typedef struct +{ + uresource_t res; + uint32_t class, palette, microtexture, fireparams; + uint16_t familyname, unusedname; + uint32_t pad1; + float diffusec, specularc, frictionc; + uint32_t footstepsound, hitsound, polyflags, untiled, lockcount, + cameracaps, pad3, pad4, pad5, pad6, pad7, pad8; + int32_t usize, vsize, datasize, colorbytes; + color_t mipzero; + uint32_t mipofs[8]; + uint8_t ubits, vbits, pad9, pad10, pad[32]; +} utexture084_t; + +typedef struct +{ + int32_t startu, startv, usize, vsize; +} fontcharacter_t; + +typedef struct +{ + udatabase_t db; + uint32_t texture; +} ufont_t; + +typedef struct +{ + udatabase_t db; +} upalette_t; + +typedef struct +{ + uresource_t res; + uint32_t class; + int32_t length, numstacktree, codeoffset, pad[5]; +} uscript_t; + +/*typedef struct +{ + udatabase_t db; + uint32_t parentclass, scripttext, script, intrinsic, actorfunc, + scripttextcrc, parenttextcrc, pad1, pad2, pad3; + actor_t defaultactor; // no way I'm defining this fucking thing + int32_t length, numstacktree, codeoffset, pad[5]; +} uclass_t;*/ + +typedef struct +{ + udatabase_t db; + uint32_t locktype, trans, staticactors, dynamicactors, collidingactors, + activeactors, unusedactors, justdeletedactors; +} uactorlist_t; + +typedef struct +{ + uresource_t res; + int32_t datasize; + int16_t familyname; + int32_t soundid; + uint8_t pad[256]; +} usound_t; + +typedef struct +{ + float x, y, z; + uint32_t flags; +} vector_t; + +typedef struct +{ + int32_t pitch, yaw, roll; +} rotator_t; + +typedef struct +{ + vector_t min, max; +} boundingrect_t; + +typedef struct +{ + boundingrect_t rect; + vector_t sphere; +} boundingvolume_t; + +typedef struct +{ + uint16_t numpolys; + uint16_t numverts; + /* everything below is unused */ + uint16_t bogusrot; + uint16_t bogusframe; + uint32_t bogusnorm[3]; + uint32_t fixscale; + uint32_t unused[3]; + uint8_t padding[12]; +} __attribute__((packed)) dataheader_t; +typedef struct +{ + uint16_t vertices[3]; + uint8_t type; + uint8_t color; /* unused */ + uint8_t uv[3][2]; + uint8_t texnum; + uint8_t flags; /* unused */ +} __attribute__((packed)) datapoly_t; +typedef struct +{ + uint16_t numframes; + uint16_t framesize; +} __attribute__((packed)) aniheader_t; + +typedef struct +{ + int16_t seqname; + int32_t startframe, numframes, rate; +} animseq_t; + +typedef struct +{ + int32_t numverttris, trilistoffset; +} vertlink_t; + +typedef struct +{ + uresource_t res; + vector_t origin; + rotator_t rotorigin; + boundingvolume_t bound; + int32_t ntris, mtris, nverts, mverts, nvertlinks, mvertlinks, + nanimframes, manimframes, nanimseqs, manimseqs; +} umesh083_t; + +typedef struct +{ + uresource_t res; + vector_t origin; + rotator_t rotorigin; + boundingrect_t bound; + int32_t ntris, mtris, nverts, mverts, nvertlinks, mvertlinks, + nanimframes, manimframes, nanimseqs, manimseqs; +} umesh084_t; + +typedef struct +{ + udatabase_t db; +} uvectors_t; + +typedef struct +{ + vector_t plane; + uint64_t zonemask; + int32_t nodeflags, ivertpool, isurf, iback, ifront, iplane, ibound; + uint8_t izone[2], numvertices, pad1; + int32_t idynamic[2]; +} bspnode_t; + +typedef struct +{ + int32_t izoneactor, unused1; + uint32_t unused2; + uint64_t connectivity, visibility, unused3; +} zoneproperties083_t; + +typedef struct +{ + int32_t izoneactor, unused; + uint64_t connectivity, visibility; +} zoneproperties084_t; + +typedef struct +{ + udatabase_t db; + uint32_t numzones, numuniqueplanes, unused1, unused2; + zoneproperties083_t zones[64]; +} ubspnodes083_t; + +typedef struct +{ + udatabase_t db; + uint32_t numzones, numuniqueplanes, unused1, unused2; + zoneproperties084_t zones[64]; +} ubspnodes084_t; + +typedef struct +{ + uint32_t texture, brush, polyflags; + int32_t pbase, vnormal, vtextureu, vtexturev, ilightmesh, ibrushpoly; + uint8_t panu, panuv; + int32_t iactor; + int16_t laststarty, lastendy; +} bspsurf_t; + +typedef struct +{ + udatabase_t db; +} ubspsurfs_t; + +typedef struct +{ + int32_t dataoffset, textureustart, texturevstart, meshusize, meshvsize, + meshspacing, numstaticlights, numdynamiclights, ilightactor[16]; + uint8_t meshshift, meshubits, meshvbits, unused; +} lightmeshindex_t; + +typedef struct +{ + uresource_t res; + int32_t numindices, numdatabytes; +} ulightmesh_t; + +typedef struct +{ + vector_t base, normal, textureu, texturev, vertex[16]; + uint32_t polyflags, brush, texture; + int16_t groupname, itemname; + int32_t numvertices, ilink, ibrushpoly; + uint8_t panu, panv, izone[2]; +} poly_t; + +typedef struct +{ + udatabase_t db; +} upolys_t; + +typedef struct +{ + vector_t scale; + float sheerrate; + int32_t sheeraxis; +} scale_t; + +typedef struct +{ + uresource_t res; + uint32_t vectors, points, bspnodes, bspsurfs, vertpool, polys, + lightmesh, bounds, terrain; + vector_t location; + rotator_t rotation; + vector_t prepivot; + scale_t scaling; + uint32_t csgoper, locktype, color, polyflags, modelflags; + vector_t postpivot; + scale_t postscale, tempscale; + boundingvolume_t bound[2]; + uint8_t pad[32]; +} umodel_t; + +typedef struct +{ + uresource_t res; + uint32_t levelstate; + uint32_t beginplay, model, actorlist, playerlist, brusharray, misc, + textblocks[16]; +} ulevel_t; + +/*typedef struct +{ +} ucamera_t; + +typedef struct +{ +} uplayer_t;*/ + +typedef struct +{ + int32_t pvertex, iside; +} vertpool_t; + +typedef struct +{ + udatabase_t db; + int32_t numsharedsides; +} uvertpool_t; + +/*typedef struct +{ +} uambient_t; + +typedef struct +{ +} utransbuffer_t;*/ + +typedef struct +{ + uresource_t res; + uint32_t mesh, maxtextures, andflags, orflags; + vector_t scale; +} umeshmap_t; + +typedef struct +{ + udatabase_t db; +} ubounds_t; + +typedef struct +{ + int32_t gridx0, gridx1, gridy0, gridy1; +} terrainindex_t; + +typedef struct +{ + udatabase_t db; + uint32_t layerflags[8], heightmaps[8], tilemaps[8], tilerefs[128]; +} uterrain_t; + +typedef struct +{ + udatabase_t db; +} uenum_t; + +typedef struct +{ + uint32_t hdrsize, recsize; // only the essentials needed +} resourcetype_t; + +// global stuff +FILE *f; +resourcefiletrailer_t tail; +nameentry_t *names; +resnamefileentry_t *imports; +resnamefileentry_t *exports; +char *headers; +char **eheaders; + +// initialized at launch +resourcetype_t types[36] = {{0}}; + +void initialize_types( void ) +{ + // RES_None + types[RES_Buffer].hdrsize = sizeof(ubuffer_t); + types[RES_Buffer].recsize = sizeof(char); + types[RES_Array].hdrsize = sizeof(uarray_t); + types[RES_Array].recsize = sizeof(uint32_t); + types[RES_TextBuffer].hdrsize = sizeof(utextbuffer_t); + types[RES_TextBuffer].recsize = sizeof(char); + if ( tail.version == RES_FILE_VERSION_083 ) + types[RES_Texture].hdrsize = sizeof(utexture083_t); + else types[RES_Texture].hdrsize = sizeof(utexture084_t); + types[RES_Texture].recsize = 0; + types[RES_Font].hdrsize = sizeof(ufont_t); + types[RES_Font].recsize = sizeof(fontcharacter_t); + types[RES_Palette].hdrsize = sizeof(upalette_t); + types[RES_Palette].recsize = sizeof(color_t); + types[RES_Script].hdrsize = sizeof(uscript_t); + types[RES_Script].recsize = 1; + // guessed, can't be bothered to declare AActor + if ( tail.version == RES_FILE_VERSION_083 ) + types[RES_Class].hdrsize = 1168; + else types[RES_Class].hdrsize = 1272; + types[RES_Class].recsize = 0; + types[RES_ActorList].hdrsize = sizeof(uactorlist_t); + types[RES_ActorList].recsize = 0; // will have to guess + types[RES_Sound].hdrsize = sizeof(usound_t); + types[RES_Sound].recsize = 1; + if ( tail.version == RES_FILE_VERSION_083 ) + types[RES_Mesh].hdrsize = sizeof(umesh083_t); + else types[RES_Mesh].hdrsize = sizeof(umesh084_t); + types[RES_Mesh].recsize = 0; + types[RES_Vectors].hdrsize = sizeof(uvectors_t); + types[RES_Vectors].recsize = sizeof(vector_t); + if ( tail.version == RES_FILE_VERSION_083 ) + types[RES_BspNodes].hdrsize = sizeof(ubspnodes083_t); + else types[RES_BspNodes].hdrsize = sizeof(ubspnodes084_t); + types[RES_BspNodes].recsize = sizeof(bspnode_t); + types[RES_BspSurfs].hdrsize = sizeof(ubspsurfs_t); + types[RES_BspSurfs].recsize = sizeof(bspsurf_t); + types[RES_LightMesh].hdrsize = sizeof(ulightmesh_t); + types[RES_LightMesh].recsize = 0; + types[RES_Polys].hdrsize = sizeof(upolys_t); + types[RES_Polys].recsize = sizeof(poly_t); + types[RES_Model].hdrsize = sizeof(umodel_t); + types[RES_Model].recsize = 0; + types[RES_Level].hdrsize = sizeof(ulevel_t); + if ( tail.version == RES_FILE_VERSION_084 ) + types[RES_Level].hdrsize += 4; // dunno why + types[RES_Level].recsize = 0; + // RES_Camera + // RES_Player + types[RES_VertPool].hdrsize = sizeof(uvertpool_t); + types[RES_VertPool].recsize = sizeof(vertpool_t); + // RES_Ambient + // RES_TransBuffer + types[RES_MeshMap].hdrsize = sizeof(umeshmap_t); + types[RES_MeshMap].recsize = 0; + types[RES_Bounds].hdrsize = sizeof(ubounds_t); + types[RES_Bounds].recsize = sizeof(boundingrect_t); + types[RES_Terrain].hdrsize = sizeof(uterrain_t); + types[RES_Terrain].recsize = sizeof(terrainindex_t); + types[RES_Enum].hdrsize = sizeof(uenum_t); + types[RES_Enum].recsize = sizeof(uint32_t); +} + +void extract_txt( uint32_t i, utextbuffer_t *txt ) +{ + mkdir("Classes",0755); + fseek(f,txt->db.res.dataofs,SEEK_SET); + char *rbuf = malloc(txt->db.res.datasize); + fread(rbuf,1,txt->db.res.datasize-1,f); + char fname[256]; + snprintf(fname,256,"Classes/%.32s.tcx",exports[i].name); + FILE *out = fopen(fname,"wb"); + fwrite(rbuf,1,txt->db.res.datasize-1,out); + fclose(out); + free(rbuf); +} + +void extract_snd( uint32_t i, usound_t *snd ) +{ + mkdir("Sounds",0755); + fseek(f,snd->res.dataofs,SEEK_SET); + char sig[4]; + fread(sig,4,1,f); + if ( !strncmp(sig,"CSNX",4) ) fseek(f,248,SEEK_CUR); + else fseek(f,-4,SEEK_CUR); + char *rbuf = malloc(snd->res.datasize); + fread(rbuf,1,snd->res.datasize-1,f); + char fname[256]; + snprintf(fname,256,"Sounds/%.32s.wav",exports[i].name); + FILE *out = fopen(fname,"wb"); + fwrite(rbuf,1,snd->res.datasize-1,out); + fclose(out); + free(rbuf); +} + + +int writepng( const char *filename, unsigned char *fdata, int fw, int fh, + png_color *fpal, int fpalsiz ) +{ + if ( !filename ) return 0; + png_structp pngp; + png_infop infp; + FILE *pf; + if ( !(pf = fopen(filename,"wb")) ) return 0; + pngp = png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0); + if ( !pngp ) + { + fclose(pf); + return 0; + } + infp = png_create_info_struct(pngp); + if ( !infp ) + { + fclose(pf); + png_destroy_write_struct(&pngp,0); + return 0; + } + if ( setjmp(png_jmpbuf(pngp)) ) + { + png_destroy_write_struct(&pngp,&infp); + fclose(pf); + return 0; + } + png_init_io(pngp,pf); + if ( fpal ) + { + png_set_IHDR(pngp,infp,fw,fh,8,PNG_COLOR_TYPE_PALETTE, + PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + png_set_PLTE(pngp,infp,fpal,fpalsiz); + unsigned char t = 0; + png_set_tRNS(pngp,infp,&t,1,0); + png_write_info(pngp,infp); + for ( int i=0; idb.res.dataofs,SEEK_SET); + fread(cpal,sizeof(color_t),256,f); + for ( int i=0; i<256; i++ ) + { + fpal[i].red = cpal[i].r; + fpal[i].green = cpal[i].g; + fpal[i].blue = cpal[i].b; + } + for ( int y=0; y<128; y++ ) for ( int x=0; x<128; x++ ) + { + dat[x+y*128] = (x/8)+(y/8)*16; + } + char fname[256]; + snprintf(fname,256,"Palettes/%.32s.png",exports[i].name); + writepng(fname,dat,128,128,fpal,256); +} + +void extract_tex083( uint32_t i, utexture083_t *tex ) +{ + mkdir("Textures",0755); + uint8_t *pxdata = malloc(tex->res.datasize); + fseek(f,tex->res.dataofs+tex->mipofs[0],SEEK_SET); + fread(pxdata,1,tex->res.datasize,f); + png_color fpal[256]; + if ( tex->palette ) + { + upalette_t *pal = (upalette_t*)eheaders[tex->palette-1]; + color_t cpal[256]; + fseek(f,pal->db.res.dataofs,SEEK_SET); + fread(cpal,sizeof(color_t),256,f); + for ( int i=0; i<256; i++ ) + { + fpal[i].red = cpal[i].r; + fpal[i].green = cpal[i].g; + fpal[i].blue = cpal[i].b; + } + } + else + { + // fallback + for ( int i=0; i<256; i++ ) + { + fpal[i].red = i; + fpal[i].green = i; + fpal[i].blue = i; + } + } + char fname[256]; + snprintf(fname,256,"Textures/%.32s.png",exports[i].name); + writepng(fname,pxdata,tex->usize,tex->vsize,&fpal[0],256); + free(pxdata); +} + +void extract_tex084( uint32_t i, utexture084_t *tex ) +{ + mkdir("Textures",0755); + uint8_t *pxdata = malloc(tex->res.datasize); + fseek(f,tex->res.dataofs+tex->mipofs[0],SEEK_SET); + fread(pxdata,1,tex->res.datasize,f); + png_color fpal[256]; + if ( tex->palette ) + { + upalette_t *pal = (upalette_t*)eheaders[tex->palette-1]; + color_t cpal[256]; + fseek(f,pal->db.res.dataofs,SEEK_SET); + fread(cpal,sizeof(color_t),256,f); + for ( int i=0; i<256; i++ ) + { + fpal[i].red = cpal[i].r; + fpal[i].green = cpal[i].g; + fpal[i].blue = cpal[i].b; + } + } + else + { + // fallback + for ( int i=0; i<256; i++ ) + { + fpal[i].red = i; + fpal[i].green = i; + fpal[i].blue = i; + } + } + char fname[256]; + snprintf(fname,256,"Textures/%.32s.png",exports[i].name); + writepng(fname,pxdata,tex->usize,tex->vsize,&fpal[0],256); + free(pxdata); +} + +void extract_msh083( uint32_t i, umesh083_t *msh ) +{ + mkdir("Models",0755); + char fname[256]; + FILE *out; + // write datafile + snprintf(fname,256,"Models/%.32s_d.3d",exports[i].name); + out = fopen(fname,"wb"); + dataheader_t dhead = + { + .numpolys = msh->mtris, + .numverts = msh->mverts + }; + fwrite(&dhead,sizeof(dataheader_t),1,out); + fseek(f,msh->res.dataofs,SEEK_SET); + datapoly_t *dpoly = calloc(sizeof(datapoly_t),dhead.numpolys); + fread(dpoly,sizeof(datapoly_t),dhead.numpolys,f); + fwrite(dpoly,sizeof(datapoly_t),dhead.numpolys,out); + free(dpoly); + fclose(out); + // write anivfile + snprintf(fname,256,"Models/%.32s_a.3d",exports[i].name); + out = fopen(fname,"wb"); + aniheader_t ahead = + { + .numframes = msh->manimframes, + .framesize = msh->mverts*4 + }; + fwrite(&ahead,sizeof(aniheader_t),1,out); + uint32_t *uverts = calloc(sizeof(uint32_t),dhead.numverts + *ahead.numframes); + fread(uverts,sizeof(uint32_t),dhead.numverts*ahead.numframes,f); + fwrite(uverts,sizeof(uint32_t),dhead.numverts*ahead.numframes,out); + free(uverts); + fclose(out); +} + +void extract_msh084( uint32_t i, umesh084_t *msh ) +{ + mkdir("Models",0755); + char fname[256]; + FILE *out; + // write datafile + snprintf(fname,256,"Models/%.32s_d.3d",exports[i].name); + out = fopen(fname,"wb"); + dataheader_t dhead = + { + .numpolys = msh->mtris, + .numverts = msh->mverts + }; + fwrite(&dhead,sizeof(dataheader_t),1,out); + fseek(f,msh->res.dataofs,SEEK_SET); + datapoly_t *dpoly = calloc(sizeof(datapoly_t),dhead.numpolys); + fread(dpoly,sizeof(datapoly_t),dhead.numpolys,f); + fwrite(dpoly,sizeof(datapoly_t),dhead.numpolys,out); + free(dpoly); + fclose(out); + // write anivfile + snprintf(fname,256,"Models/%.32s_a.3d",exports[i].name); + out = fopen(fname,"wb"); + aniheader_t ahead = + { + .numframes = msh->manimframes, + .framesize = msh->mverts*4 + }; + fwrite(&ahead,sizeof(aniheader_t),1,out); + uint32_t *uverts = calloc(sizeof(uint32_t),dhead.numverts + *ahead.numframes); + fread(uverts,sizeof(uint32_t),dhead.numverts*ahead.numframes,f); + fwrite(uverts,sizeof(uint32_t),dhead.numverts*ahead.numframes,out); + free(uverts); + fclose(out); +} + +int main( int argc, char **argv ) +{ + if ( argc < 2 ) + { + fprintf(stderr,"usage: u083extract \n"); + return 0; + } + f = fopen(argv[1],"rb"); + if ( !f ) + { + fprintf(stderr,"Cannot open %s: %s\n",argv[1],strerror(errno)); + return 1; + } + fseek(f,-sizeof(resourcefiletrailer_t),SEEK_END); + fread(&tail,sizeof(resourcefiletrailer_t),1,f); + if ( strncmp(tail.tag,RES_FILE_TAG,32) ) + { + fprintf(stderr,"Not a valid Unreal Resource file.\n"); + fclose(f); + return 1; + } + if ( (tail.version != RES_FILE_VERSION_083) + && (tail.version != RES_FILE_VERSION_084) ) + { + fprintf(stderr,"Unreal Resource version %04x not supported.\n", + tail.version); + fclose(f); + return 1; + } + printf("%s - %u names, %u imports, %u exports, %u bytes of headers\n", + argv[1],tail.nnames,tail.nimports,tail.nexports,tail.lheaders); + names = calloc(tail.nnames,sizeof(nameentry_t)); + imports = calloc(tail.nimports,sizeof(resnamefileentry_t)); + exports = calloc(tail.nexports,sizeof(resnamefileentry_t)); + headers = calloc(tail.lheaders,1); + fseek(f,tail.onames,SEEK_SET); + fread(names,sizeof(nameentry_t),tail.nnames,f); + fseek(f,tail.oimports,SEEK_SET); + fread(imports,sizeof(resnamefileentry_t),tail.nimports,f); + fseek(f,tail.oexports,SEEK_SET); + fread(exports,sizeof(resnamefileentry_t),tail.nexports,f); + fseek(f,tail.oheaders,SEEK_SET); + fread(headers,1,tail.lheaders,f); + initialize_types(); + eheaders = calloc(tail.nexports,sizeof(char*)); + char *ptr = headers, *prev = 0; + for ( int i=0; i +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RES_FILE_TAG "Unrealfile\x1A" + +typedef struct +{ + char tag[32]; + uint32_t version, nobjects, nnames, onames, oobjects, ndeps; +} unrealheader_t; + +typedef struct +{ + char name[32]; + uint32_t flags; +} unrealname_t; + +typedef struct +{ + int16_t name, pkg, class; + uint32_t flags, headerofs, crc, dataofs, headersiz, datasiz; +} unrealobject_t; + +FILE *f; +unrealheader_t head; +unrealname_t *names; +unrealobject_t *objects; + +void readstr( char *dest, int max, FILE *f ) +{ + int cnt = 0; + while ( cnt < max ) + { + fread(&dest[cnt],1,1,f); + if ( !dest[cnt] ) return; + cnt++; + } +} + +void debug_printheader( uint32_t i ) +{ + uint8_t *header = malloc(objects[i].headersiz); + fseek(f,objects[i].headerofs,SEEK_SET); + fread(header,objects[i].headersiz,1,f); + int j = 0; + printf("%.32s\'%.32s\' header (%u):\n",names[objects[i].class-1].name, + names[objects[i].name-1].name,objects[i].headersiz); + for ( j=0; j= 27 ) fofs = 34; + else if ( head.version >= 25 ) fofs = 0; + fseek(f,objects[i].headerofs+fofs,SEEK_SET); + int16_t family; + fread(&family,2,1,f); + fseek(f,objects[i].dataofs,SEEK_SET); + char sig[4]; + fread(sig,4,1,f); + if ( !strncmp(sig,"CSNX",4) ) fseek(f,248,SEEK_CUR); + else fseek(f,-4,SEEK_CUR); + char *rbuf = malloc(objects[i].datasiz); + fread(rbuf,1,objects[i].datasiz-1,f); + char fname[256]; + if ( family > 0 ) snprintf(fname,256,"Sounds/%.32s.%.32s.wav", + names[family-1].name,names[objects[i].name-1].name); + else snprintf(fname,256,"Sounds/%.32s.wav", + names[objects[i].name-1].name); + FILE *out = fopen(fname,"wb"); + fwrite(rbuf,1,objects[i].datasiz-1,out); + fclose(out); + free(rbuf); +} + +int writepng( const char *filename, unsigned char *fdata, int fw, int fh, + png_color *fpal, int fpalsiz ) +{ + if ( !filename ) return 0; + png_structp pngp; + png_infop infp; + FILE *pf; + if ( !(pf = fopen(filename,"wb")) ) return 0; + pngp = png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0); + if ( !pngp ) + { + fclose(pf); + return 0; + } + infp = png_create_info_struct(pngp); + if ( !infp ) + { + fclose(pf); + png_destroy_write_struct(&pngp,0); + return 0; + } + if ( setjmp(png_jmpbuf(pngp)) ) + { + png_destroy_write_struct(&pngp,&infp); + fclose(pf); + return 0; + } + png_init_io(pngp,pf); + if ( fpal ) + { + png_set_IHDR(pngp,infp,fw,fh,8,PNG_COLOR_TYPE_PALETTE, + PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + png_set_PLTE(pngp,infp,fpal,fpalsiz); + unsigned char t = 0; + png_set_tRNS(pngp,infp,&t,1,0); + png_write_info(pngp,infp); + for ( int i=0; i= 27 ) oofs += 8; + else if ( head.version >= 25 ) oofs += 2; + int32_t pal; + fseek(f,objects[i].headerofs+oofs,SEEK_SET); + fread(&pal,4,1,f); + uint32_t w, h; + if ( head.version >= 27 ) oofs += 21; + else if ( head.version >= 25 ) oofs += 8; + fseek(f,objects[i].headerofs+oofs+38,SEEK_SET); + fread(&w,4,1,f); + fread(&h,4,1,f); + uint32_t mipofs; + fseek(f,objects[i].headerofs+oofs+66,SEEK_SET); + fread(&mipofs,4,1,f); + if ( mipofs == 0xffffffff ) mipofs = 0; // why + uint8_t *pxdata = malloc(w*h); + fseek(f,objects[i].dataofs+mipofs,SEEK_SET); + fread(pxdata,1,w*h,f); + png_color fpal[256]; + if ( pal ) + { + color_t cpal[256]; + fseek(f,objects[pal-1].dataofs,SEEK_SET); + fread(cpal,sizeof(color_t),256,f); + for ( int i=0; i<256; i++ ) + { + fpal[i].red = cpal[i].r; + fpal[i].green = cpal[i].g; + fpal[i].blue = cpal[i].b; + } + } + else + { + // fallback + for ( int i=0; i<256; i++ ) + { + fpal[i].red = i; + fpal[i].green = i; + fpal[i].blue = i; + } + } + char fname[256]; + snprintf(fname,256,"Textures/%.32s.png",names[objects[i].name-1].name); + writepng(fname,pxdata,w,h,&fpal[0],256); + free(pxdata); +} + +typedef struct +{ + uint16_t numpolys; + uint16_t numverts; + /* everything below is unused */ + uint16_t bogusrot; + uint16_t bogusframe; + uint32_t bogusnorm[3]; + uint32_t fixscale; + uint32_t unused[3]; + uint8_t padding[12]; +} __attribute__((packed)) dataheader_t; +typedef struct +{ + uint16_t vertices[3]; + uint8_t type; + uint8_t color; /* unused */ + uint8_t uv[3][2]; + uint8_t texnum; + uint8_t flags; /* unused */ +} __attribute__((packed)) datapoly_t; +typedef struct +{ + uint16_t numframes; + uint16_t framesize; +} __attribute__((packed)) aniheader_t; + +void extract_msh( uint32_t i ) +{ + mkdir("Models",0755); + int dofs = 16; + int oofs = 57; + if ( head.version >= 27 ) + { + oofs += 20; + dofs += 8; + } + else if ( head.version >= 25 ) + { + dofs += 2; + oofs += 2; + } + int32_t verts, tris, seqs; + uint32_t nverts, nframes, tverts, npolys; + fseek(f,objects[i].headerofs+oofs,SEEK_SET); + fread(&verts,4,1,f); + fread(&tris,4,1,f); + fread(&seqs,4,1,f); + fseek(f,objects[i].headerofs+oofs+32,SEEK_SET); + fread(&nverts,4,1,f); + fread(&nframes,4,1,f); + fseek(f,objects[verts-1].headerofs+dofs,SEEK_SET); + fread(&tverts,4,1,f); + fseek(f,objects[tris-1].headerofs+dofs,SEEK_SET); + fread(&npolys,4,1,f); + char fname[256]; + FILE *out; + // write datafile + snprintf(fname,256,"Models/%.32s_d.3d",names[objects[i].name-1].name); + out = fopen(fname,"wb"); + dataheader_t dhead = + { + .numpolys = npolys, + .numverts = nverts, + }; + fwrite(&dhead,sizeof(dataheader_t),1,out); + fseek(f,objects[tris-1].dataofs,SEEK_SET); + datapoly_t *dpoly = calloc(sizeof(datapoly_t),dhead.numpolys); + fread(dpoly,sizeof(datapoly_t),dhead.numpolys,f); + fwrite(dpoly,sizeof(datapoly_t),dhead.numpolys,out); + free(dpoly); + fclose(out); + // write anivfile + snprintf(fname,256,"Models/%.32s_a.3d",names[objects[i].name-1].name); + out = fopen(fname,"wb"); + aniheader_t ahead = + { + .numframes = nframes, + .framesize = nverts*4, + }; + fwrite(&ahead,sizeof(aniheader_t),1,out); + fseek(f,objects[verts-1].dataofs,SEEK_SET); + uint32_t *uverts = calloc(sizeof(uint32_t),dhead.numverts + *ahead.numframes); + fread(uverts,sizeof(uint32_t),dhead.numverts*ahead.numframes,f); + fwrite(uverts,sizeof(uint32_t),dhead.numverts*ahead.numframes,out); + free(uverts); + fclose(out); +} + +int main( int argc, char **argv ) +{ + if ( argc < 2 ) + { + fprintf(stderr,"usage: u086extract \n"); + return 0; + } + f = fopen(argv[1],"rb"); + if ( !f ) + { + fprintf(stderr,"Cannot open %s: %s\n",argv[1],strerror(errno)); + return 1; + } + fread(&head,sizeof(unrealheader_t),1,f); + if ( strncmp(head.tag,RES_FILE_TAG,32) ) + { + fprintf(stderr,"Not a valid Unreal Resource file.\n"); + fclose(f); + return 1; + } + printf("%s - version %u, %u names (&%x), %u objects (&%x)," + " %u dependencies\n",argv[1],head.version,head.nnames, + head.onames,head.nobjects,head.oobjects,head.ndeps); + names = calloc(head.nnames,sizeof(unrealname_t)); + fseek(f,head.onames,SEEK_SET); + for ( int i=0; i= 25 ) fread(&objects[i].pkg,2,1,f); + fread(&objects[i].class,2,1,f); + fread(&objects[i].flags,4,2,f); + if ( objects[i].headerofs != 0 ) fread(&objects[i].crc,4,4,f); + if ( !strcmp(names[objects[i].class-1].name,"Class") ) + fseek(f,14,SEEK_CUR); // haven't figured this out yet + } + /*for ( int i=0; i +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NAMES_MARK "[REFERENCED NAMES]" +#define IMPORT_MARK "[IMPORTED RESOURCES]" +#define EXPORT_MARK "[EXPORTED RESOURCES]" +#define DATA_MARK "[EXPORTED RESOURCE DATA]" +#define HEADER_MARK "[EXPORTED RESOURCE HEADERS]" +#define TABLE_MARK "[EXPORTED RESOURCE TABLE]" +#define TRAILER_MARK "[SUMMARY]" +#define RES_FILE_TAG "Unreal Resource\0" + +enum EResourceType +{ + RES_None = 0, // No resource. + RES_Buffer = 1, // A large binary object holding misc saveable data. + RES_Array = 3, // An array of resources. + RES_TextBuffer = 4, // A text buffer. + RES_Texture = 5, // Texture or compound texture. + RES_Font = 6, // Font for use in game. + RES_Palette = 7, // A palette. + RES_Script = 9, // Script. + RES_Class = 10, // An actor class. + RES_ActorList = 11, // An array of actors. + RES_Sound = 12, // Sound effect. + RES_Mesh = 14, // Animated mesh. + RES_Vectors = 16, // 32-bit floating point vector list. + RES_BspNodes = 17, // Bsp node list. + RES_BspSurfs = 18, // Bsp polygon list. + RES_LightMesh = 19, // Bsp polygon lighting mesh. + RES_Polys = 20, // Editor polygon list. + RES_Model = 21, // Model or level map. + RES_Level = 22, // A game level. + RES_Camera = 25, // A rendering camera on this machine. + RES_Player = 28, // A remote player logged into the local server. + RES_VertPool = 29, // A vertex pool corresponding to a Bsp and FPoints/FVectors table. + RES_Ambient = 30, // An ambient sound definition. + RES_TransBuffer = 31, // Transaction tracking buffer. + RES_MeshMap = 32, // MeshMap. + RES_Bounds = 33, // Bounding structure. + RES_Terrain = 34, // Terrain. + RES_Enum = 35, // Enumeration (array of FName's). +}; + +typedef struct +{ + uint16_t version, nexports, nimports, nnames; + uint32_t onames, oimports, oheaders, oexports; + uint8_t pad[32]; + char tag[16]; +} __attribute__((packed)) resourcefiletrailer_t; + +typedef struct +{ + char name[16]; + uint16_t unused; +} __attribute__((packed)) nameentry_t; + +typedef struct +{ + char name[16]; + uint16_t index; + uint8_t type, unknown; + uint8_t pad1[6]; + uint16_t headersize; + uint8_t pad2[10]; + uint32_t dataofs, datasize; + uint8_t pad3[8]; +} __attribute__((packed)) resnamefileentry_t; + +typedef struct +{ + uint8_t r, g, b, x; +} __attribute__((packed)) color_t; + +// global stuff +FILE *f; +resourcefiletrailer_t tail; +nameentry_t *names; +resnamefileentry_t *imports; +resnamefileentry_t *exports; +size_t *eheaders; + +int writepng( const char *filename, unsigned char *fdata, int fw, int fh, + png_color *fpal, int fpalsiz ) +{ + if ( !filename ) return 0; + png_structp pngp; + png_infop infp; + FILE *pf; + if ( !(pf = fopen(filename,"wb")) ) return 0; + pngp = png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0); + if ( !pngp ) + { + fclose(pf); + return 0; + } + infp = png_create_info_struct(pngp); + if ( !infp ) + { + fclose(pf); + png_destroy_write_struct(&pngp,0); + return 0; + } + if ( setjmp(png_jmpbuf(pngp)) ) + { + png_destroy_write_struct(&pngp,&infp); + fclose(pf); + return 0; + } + png_init_io(pngp,pf); + if ( fpal ) + { + png_set_IHDR(pngp,infp,fw,fh,8,PNG_COLOR_TYPE_PALETTE, + PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + png_set_PLTE(pngp,infp,fpal,fpalsiz); + unsigned char t = 0; + png_set_tRNS(pngp,infp,&t,1,0); + png_write_info(pngp,infp); + for ( int i=0; i\n"); + return 0; + } + f = fopen(argv[1],"rb"); + if ( !f ) + { + fprintf(stderr,"Cannot open %s: %s\n",argv[1],strerror(errno)); + return 1; + } + fseek(f,-sizeof(resourcefiletrailer_t),SEEK_END); + fread(&tail,sizeof(resourcefiletrailer_t),1,f); + if ( strncmp(tail.tag,RES_FILE_TAG,16) ) + { + fprintf(stderr,"Not a valid Unreal Resource file.\n"); + fclose(f); + return 1; + } + if ( tail.version != 1 ) + { + fprintf(stderr,"Unreal Resource version %04x not supported.\n", + tail.version); + fclose(f); + return 1; + } + printf("%s - %u names, %u imports, %u exports\n",argv[1],tail.nnames, + tail.nimports,tail.nexports); + names = calloc(tail.nnames,sizeof(nameentry_t)); + imports = calloc(tail.nimports,sizeof(resnamefileentry_t)); + exports = calloc(tail.nexports,sizeof(resnamefileentry_t)); + eheaders = calloc(tail.nexports,sizeof(size_t)); + fseek(f,tail.onames,SEEK_SET); + fread(names,sizeof(nameentry_t),tail.nnames,f); + fseek(f,tail.oimports,SEEK_SET); + fread(imports,sizeof(resnamefileentry_t),tail.nimports,f); + fseek(f,tail.oexports,SEEK_SET); + fread(exports,sizeof(resnamefileentry_t),tail.nexports,f); + fseek(f,tail.oheaders,SEEK_SET); + for ( int i=0; ipkgver >= 60 ) fpos += 4; + if ( head->pkgver >= 55 ) fpos += 4; else readindex(); return readindex(); } @@ -170,7 +170,7 @@ void readimport2( int32_t *cpkg, int32_t *cname, int32_t *pkg, int32_t *name ) { *cpkg = readindex(); *cname = readindex(); - if ( head->pkgver >= 60 ) *pkg = readdword(); + if ( head->pkgver >= 55 ) *pkg = readdword(); else *pkg = readindex(); *name = readindex(); } @@ -190,7 +190,7 @@ void readexport( int32_t *class, int32_t *ofs, int32_t *siz, int32_t *name ) { *class = readindex(); readindex(); - if ( head->pkgver >= 60 ) fpos += 4; + if ( head->pkgver >= 55 ) fpos += 4; *name = readindex(); fpos += 4; *siz = readindex(); @@ -213,7 +213,7 @@ void readexport2( int32_t *class, int32_t *super, int32_t *pkg, int32_t *name, { *class = readindex(); *super = readindex(); - if ( head->pkgver >= 60 ) *pkg = readdword(); + if ( head->pkgver >= 55 ) *pkg = readdword(); else *pkg = readindex(); *name = readindex(); *flags = readdword(); @@ -281,7 +281,7 @@ typedef struct typedef struct { - uint8_t texcount; + uint32_t texcount; ufonttex_t *tex; } ufonthdr_t; @@ -296,12 +296,14 @@ void savefont( int32_t namelen, char *name ) } ufonthdr_t fhead; memset(&fhead,0,sizeof(ufonthdr_t)); - fhead.texcount = readbyte(); + fhead.texcount = readindex(); fhead.tex = calloc(fhead.texcount,sizeof(ufonttex_t)); + uint32_t tchars = 0; for ( int i=0; i 256 ) fprintf(f,"0x%04x: ",cc); + else fprintf(f,"0x%02x: ",cc); construct_fullname(f,fhead.tex[i].texture); fprintf(f," (%d,%d)-(%d,%d)\n",fhead.tex[i].chars[j].x, fhead.tex[i].chars[j].y,fhead.tex[i].chars[j].w, @@ -413,8 +414,10 @@ int main( int argc, char **argv ) // begin reading data size_t prev = fpos; fpos = ofs; - if ( head->pkgver < 40 ) fpos += 8; - if ( head->pkgver < 60 ) fpos += 16; + if ( head->pkgver < 45 ) fpos += 4; + if ( head->pkgver < 55 ) fpos += 16; + if ( head->pkgver <= 44 ) fpos -= 6; // ??? + if ( head->pkgver <= 35 ) fpos += 8; // ??? int32_t prop = readindex(); if ( (uint32_t)prop >= head->nnames ) { @@ -427,6 +430,8 @@ retry: if ( strncasecmp(pname,"none",l) ) { uint8_t info = readbyte(); + int array = info&0x80; + int type = info&0xf; int psiz = (info>>4)&0x7; switch ( psiz ) { @@ -455,6 +460,10 @@ retry: psiz = readdword(); break; } + if ( array && (type != 3) ) + readindex(); + if ( type == 10 ) + readindex(); // skip struct name fpos += psiz; printf(" Skipping property %.*s\n",l,pname); prop = readindex(); diff --git a/unrundeleter.c b/unrundeleter.c new file mode 100644 index 0000000..256f33f --- /dev/null +++ b/unrundeleter.c @@ -0,0 +1,361 @@ +#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; +} + +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 >= 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; +} + +// 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 >= 60 ) *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 >= 60 ) 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 >= 60 ) *pkg = readdword(); + else *pkg = readindex(); + *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; +} + +int main( int argc, char **argv ) +{ + if ( argc < 2 ) + { + printf("Usage: unrundeleter \n"); + return 1; + } + int fd = open(argv[1],O_RDWR); + 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("bDeleteMe") ) + { + printf("Package %s does not contain deleted exports\n",argv[1]); + free(pkgfile); + return 4; + } + // loop through exports and search for bDeleteMe properties + fpos = head->oexports; + for ( uint32_t i=0; inexports; i++ ) + { + int32_t class, ofs, siz, name; + readexport(&class,&ofs,&siz,&name); + if ( (siz <= 0) || (class >= 0) ) continue; + // check the class + class = -class-1; + if ( (uint32_t)class > head->nimports ) continue; + // read name + int32_t l = 0; + char *ename = (char*)(pkgfile+getname(name,&l)); + printf("checking %.*s\n",l,ename); + // 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 ( (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 ptype = info&0xf; + int psiz = (info>>4)&0x7; + int parr = (info>>7)&0x1; + 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; + } + fpos += psiz; + printf(" %zu: Skipping property %.*s of size %d and type %d (%d)\n",fpos,l,pname,psiz,ptype,parr); + if ( (ptype != 3) && parr ) + { + printf("%d\n",readbyte()); + return 1; + } + prop = readindex(); + printf("prop: %d\n",prop); + pname = (char*)(pkgfile+getname(prop,&l)); + // TODO figure out how the fuck structs and arrays actually are structured + // because the documentation for this is illegible + if ( ptype == 10 ) + { + + } + goto retry; + } + fpos = prev; + } + free(pkgfile); + return 0; +} diff --git a/usndextract.c b/usndextract.c new file mode 100644 index 0000000..805680b --- /dev/null +++ b/usndextract.c @@ -0,0 +1,420 @@ +#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 = readindex(); + *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 savesound( int32_t namelen, char *name, int version ) +{ + char fname[256] = {0}; + int32_t fmt = readindex(); // not really needed, always assume wav + int32_t grp, grouplen; + char *group; + if ( version > 35 ) + { + // ???? + readindex(); + readindex(); + grp = readindex(); + grouplen = 0; + group = (char*)(pkgfile+getname(grp,&grouplen)); + // ???? + readindex(); + } + else + { + grp = readindex(); + grouplen = 0; + group = (char*)(pkgfile+getname(grp,&grouplen)); + readindex(); // unknown + } + uint32_t ofsnext = 0; + if ( version >= 63 ) ofsnext = readdword(); // not needed but gotta read + // the actual important info starts now + int32_t sndsize = readindex(); + char *snddata = (char*)(pkgfile+fpos); + if ( strncmp(group,"None",grouplen) ) snprintf(fname,256,"%.*s.%.*s.wav",grouplen,group,namelen,name); + else snprintf(fname,256,"%.*s.wav",namelen,name); + FILE *f = fopen(fname,"wb"); + fwrite(snddata,sndsize,1,f); + fclose(f); +} + +int main( int argc, char **argv ) +{ + if ( argc < 2 ) + { + printf("Usage: usndextract \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("Sound") ) + { + printf("Package %s does not contain sounds\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, ofs, siz, name; + readexport(&class,&ofs,&siz,&name); + 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,"Sound",l); + if ( !ismesh ) continue; + char *snd = (char*)(pkgfile+getname(name,&l)); + printf("Sound 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 <= 35 ) fpos += 8; // ??? + // only very old packages have properties for sound classes + if ( head->pkgver > 35 ) + goto noprop; + // 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; + 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 ( array && (type != 3) ) + { + int idx = readindex(); + printf(" index: %d\n",idx); + } + if ( type == 10 ) + { + int32_t tl, sn; + sn = readindex(); + char *sname = (char*)(pkgfile+getname(sn,&tl)); + printf(" struct: %.*s\n",tl,sname); + } + fpos += psiz; + prop = readindex(); + pname = (char*)(pkgfile+getname(prop,&l)); + goto retry; + } +noprop: + savesound(sndl,snd,head->pkgver); + fpos = prev; + } + free(pkgfile); + return 0; +} diff --git a/utxextract.c b/utxextract.c new file mode 100644 index 0000000..e9857b2 --- /dev/null +++ b/utxextract.c @@ -0,0 +1,581 @@ +#include +#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 = readindex(); + *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; +} + +int writepng( const char *filename, unsigned char *fdata, int fw, int fh, + png_color *fpal, int fpalsiz, int masked ) +{ + if ( !filename ) return 0; + png_structp pngp; + png_infop infp; + FILE *pf; + if ( !(pf = fopen(filename,"wb")) ) return 0; + pngp = png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0); + if ( !pngp ) + { + fclose(pf); + return 0; + } + infp = png_create_info_struct(pngp); + if ( !infp ) + { + fclose(pf); + png_destroy_write_struct(&pngp,0); + return 0; + } + if ( setjmp(png_jmpbuf(pngp)) ) + { + png_destroy_write_struct(&pngp,&infp); + fclose(pf); + return 0; + } + png_init_io(pngp,pf); + if ( fpal ) + { + png_set_IHDR(pngp,infp,fw,fh,8,PNG_COLOR_TYPE_PALETTE, + PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + png_set_PLTE(pngp,infp,fpal,fpalsiz); + if ( masked ) + { + unsigned char t = 0; + png_set_tRNS(pngp,infp,&t,1,0); + } + png_write_info(pngp,infp); + for ( int i=0; ioexports; + for ( uint32_t i=0; inexports; i++ ) + { + int32_t class, ofs, siz, name; + readexport(&class,&ofs,&siz,&name); + if ( i != pal-1 ) continue; + int32_t l = 0; + char *n = (char*)(pkgfile+getname(name,&l)); + // 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 <= 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; + } + 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; + } + if ( (head->pkgver <= 56) ) + { + // group? + fpos++; + readindex(); + } + *num = readindex(); + printf(" palette: %u colors\n",*num); + *col = calloc(sizeof(color_t),*num); + memcpy(*col,pkgfile+fpos,*num*sizeof(color_t)); + fpos = prev; + return; + } + fpos = prev; +} + +void savetexture( int32_t namelen, char *name, int32_t pal, int masked, + int version ) +{ + uint32_t ncolors = 256; + color_t *paldata = 0; + readpalette(pal,&ncolors,&paldata); + if ( version <= 56 ) + { + // group? + fpos++; + readindex(); + } + uint32_t mipcnt = readbyte(); + printf(" %u mips\n",mipcnt); + uint32_t ofs = 0; + if ( version >= 63 ) ofs = readdword(); + uint32_t datasiz = readindex(); + printf(" %u size\n",datasiz); + uint8_t *imgdata = malloc(datasiz); + memcpy(imgdata,pkgfile+fpos,datasiz); + imgdata = malloc(datasiz); + memcpy(imgdata,pkgfile+fpos,datasiz); + if ( version >= 63 ) fpos = ofs; + else fpos += datasiz; + uint32_t w = readdword(); + uint32_t h = readdword(); + png_color fpal[256] = {{0}}; + if ( !paldata || (ncolors <= 0) ) + { + ncolors = 256; + // generate dummy palette + for ( int i=0; i<256; i++ ) + { + fpal[i].red = i; + fpal[i].green = i; + fpal[i].blue = i; + } + } + else + { + for ( int i=0; i\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, ofs, siz, name; + readexport(&class,&ofs,&siz,&name); + 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,"Texture",l); + if ( !istex ) continue; + char *tex = (char*)(pkgfile+getname(name,&l)); + printf("Texture 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 <= 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)); + int masked = 0; // and this (which may not be available) +retry: + 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; + } + //printf(" prop %.*s (%u, %u, %u, %u)\n",l,pname,array,type,(info>>4)&7,psiz); + if ( array && (type != 3) ) + { + int idx = readindex(); + //printf(" index: %d\n",idx); + } + if ( !strncasecmp(pname,"Palette",l) ) + pal = readindex(); + if ( !strncasecmp(pname,"bMasked",l) ) + masked = array; + else + { + if ( type == 10 ) + { + int32_t tl, sn; + sn = readindex(); + //char *sname = (char*)(pkgfile+getname(sn,&tl)); + //printf(" struct: %.*s\n",tl,sname); + } + fpos += psiz; + } + prop = readindex(); + pname = (char*)(pkgfile+getname(prop,&l)); + goto retry; + } + if ( !pal ) continue; + savetexture(texl,tex,pal,masked,head->pkgver); + fpos = prev; + } + free(pkgfile); + return 0; +} diff --git a/wavrip.c b/wavrip.c new file mode 100644 index 0000000..a52c103 --- /dev/null +++ b/wavrip.c @@ -0,0 +1,46 @@ +#include +#include +#include + +#define min(a,b) (((a)<(b))?(a):(b)) + +int main( int argc, char **argv ) +{ + if ( argc < 2 ) return 1; + FILE *fin, *fout; + if ( !(fin = fopen(argv[1],"rb")) ) + return 2; + unsigned char buf[131072]; + size_t nread = 0, pread = 0; + int i = 0; +keepreading: + nread = fread(buf,1,4,fin); + if ( feof(fin) || (nread < 4) ) + { + fclose(fin); + return 0; + } + if ( strncmp(buf,"RIFF",4) ) + { + fseek(fin,-3,SEEK_CUR); + goto keepreading; + } + char fname[256]; + snprintf(fname,256,"SND%d.wav",i); + fout = fopen(fname,"wb"); + fwrite(buf,1,4,fout); + nread = fread(buf,4,1,fin); + fwrite(buf,1,4,fout); + pread = (*(uint32_t*)buf)-4; + while ( pread > 0 ) + { + nread = fread(buf,1,min(131072,pread),fin); + fwrite(buf,1,nread,fout); + pread -= nread; + } + fclose(fout); + i++; + if ( !feof(fin) ) goto keepreading; + fclose(fin); + return 0; +}