/* * QT AGI Studio :: Copyright (C) 2000 Helen Zommer * * A big part of this code was adapted from the Windows AGI Studio * developed by Peter Kelly. * * LZW decompression code belongs to Lance Ewing. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include "menu.h" #include "util.h" #include "resources.h" #include "agicommands.h" #include #include #include #ifdef _WIN32 #include #include #include #undef TEXT #include "game.h" #define TEXT 6 #else #include #include #include "game.h" #endif #include #include #include #include #include #include #include const char *ResTypeName[4] = {"logic","picture","view","sound"}; const char *ResTypeAbbrv[4] = {"log","pic","view","snd"}; static const char *files[5] = {"vol.0","viewdir","logdir","snddir","picdir"}; Game *game; static TResource CompressedResource; /******************************* LZW variables ****************************/ #define MAXBITS 12 #define TABLE_SIZE 18041 #define START_BITS 9 static int BITS, MAX_VALUE, MAX_CODE; static unsigned int *prefix_code; static byte *append_character; static byte decode_stack[4000]; /* Holds the decoded string */ static int input_bit_count=0; /* Number of bits in input bit buffer */ static unsigned long input_bit_buffer=0L; //******************************************* const char EncryptionKey[] = "Avis Durgan"; TResourceInfo ResourceInfo[4][256]; TResource ResourceData; /* global buffer used for all resource I/0 ! (for this reason it wouldn't be wise to run several I/O operations simultaneously, like clicking compile very fast in several logic editor windows ("compile all" is ok - it is sequential) - fortunately the program works too fast to allow the user to do it...) */ //******************************************* Game::Game() { ResourceData.Data = (byte *)malloc(MaxResourceSize); defaults(); read_settings(); } //******************************************* int Game::open(string name) { byte DirData[3080]; int CurResType,CurResNum,NumDirEntries; long DirSize=-1,DirOffset=-1; byte byte1,byte2,byte3; FILE *fptr; dir=name; ID = FindAGIV3GameID(dir.c_str()); // 'V2' if not found if(ID.length() == 0)return 1; isV3 = (ID != "V2"); bool ErrorOccured = false; for(CurResType = 0;CurResType <= 3;CurResType++){ for(CurResNum = 0;CurResNum <= 255; CurResNum++){ ResourceInfo[CurResType][CurResNum].Exists = false; } } if(!isV3){ //for V2 game: open logdir, picdir, viewdir, snddir for(CurResType = 0;CurResType <= 3; CurResType++){ string DIRFilename = dir + "/"+ResTypeAbbrv[CurResType] + "dir"; fptr=fopen(DIRFilename.c_str(),"rb"); if(fptr==NULL){ menu->errmes("Error: can't open %s !",DIRFilename.c_str()); ErrorOccured = true; break; } else{ struct stat buf; fstat(fileno(fptr),&buf); int size=buf.st_size; if(size > 768){ menu->errmes("Error: %s is too big (should not be mode than 768 bytes) !",DIRFilename.c_str()); ErrorOccured = true; break; } else{ //read resource info fread(DirData,size,1,fptr); for(CurResNum = 0;CurResNum <= size/3 -1;CurResNum++){ byte1 = DirData[CurResNum*3]; byte2 = DirData[CurResNum*3+1]; byte3 = DirData[CurResNum*3+2]; if (!(byte1==0xff && byte2==0xff && byte3==0xff)){ ResourceInfo[CurResType][CurResNum].Loc = (byte1 % 16)*0x10000 + byte2*0x100 + byte3; sprintf(ResourceInfo[CurResType][CurResNum].Filename,"vol.%d",byte1/16); ResourceInfo[CurResType][CurResNum].Exists = true; } } } fclose(fptr); } } } else{ //V3 game: open [GAME_ID]dir (e.g. grdir, mhdir) string DIRFilename = dir + "/"+ID + "dir"; fptr=fopen(DIRFilename.c_str(),"rb"); if(fptr==NULL){ menu->errmes("Error: can't open %s ! ",DIRFilename.c_str()); ErrorOccured = true; } else{ struct stat buf; fstat(fileno(fptr),&buf); int size=buf.st_size; if(size > 3080){ menu->errmes("Error: %s is too big (should not be mode than 3080 bytes) !",DIRFilename.c_str()); ErrorOccured = true; } else{ fread(DirData,size,1,fptr); //read resource info for(CurResType = 0;CurResType <= 3;CurResType++){ if(!ErrorOccured){ switch(CurResType){ case LOGIC: DirOffset = DirData[0] + DirData[1]*256; DirSize = DirData[2] + DirData[3]*256 - DirOffset; break; case PICTURE: DirOffset = DirData[2] + DirData[3]*256; DirSize = DirData[4] + DirData[5]*256 - DirOffset; break; case VIEW: DirOffset = DirData[4] + DirData[5]*256; DirSize = DirData[6] + DirData[7]*256 - DirOffset; break; case SOUND: DirOffset = DirData[6] + DirData[7]*256; DirSize = size - DirOffset; break; } if(DirOffset < 0 || DirSize < 0){ menu->errmes("Error: DIR file is invalid."); ErrorOccured = true; break; } else if(DirOffset + DirSize > size){ menu->errmes("Error: Directory is beyond end of %s file !",DIRFilename.c_str()); ErrorOccured = true; break; } else{ if (DirSize > 768)NumDirEntries = 256; else NumDirEntries = DirSize/3; if (NumDirEntries > 0){ for(CurResNum = 0;CurResNum < NumDirEntries;CurResNum++){ byte1 = DirData[DirOffset+CurResNum*3]; byte2 = DirData[DirOffset+CurResNum*3+1]; byte3 = DirData[DirOffset+CurResNum*3+2]; if (!(byte1==0xff && byte2==0xff && byte3==0xff)){ ResourceInfo[CurResType][CurResNum].Loc = (byte1 % 16)*0x10000 + byte2*0x100 + byte3; sprintf(ResourceInfo[CurResType][CurResNum].Filename,"%svol.%d",ID.c_str(),byte1/16); ResourceInfo[CurResType][CurResNum].Exists = true; } } } } } } } fclose(fptr); } } if(!ErrorOccured){ AGIVersionNumber = GetAGIVersionNumber(); //printf("AGIVersion = %f\n",AGIVersionNumber); CorrectCommands(AGIVersionNumber); isOpen = true; make_source_dir(); menu->status->message(dir.c_str()); return 0; } else return 1; } //******************************************* int copy(char *src,char *dest) //copy src file to dest file (to avoid using an external 'cp' command) { FILE *filer, *filew; size_t numr; char buffer[1024]; filer=fopen(src,"rb"); if(!filer) { menu->errmes("Can't open src file %s !",src); return 1; } filew=fopen(dest,"wb"); if(!filew) { menu->errmes("Can't open dst file %s !",dest); return 1; } while(feof(filer)==0) { if((numr=fread(buffer,1,1024,filer))!=1024) { if(ferror(filer)!=0) { menu->errmes("read file error: %s !",src); return 1; } } if(fwrite(buffer,1,numr,filew) != numr) { menu->errmes("write file error: %s !",dest); return 1; } } fclose(filew); fclose(filer); return 0; } //******************************************* int Game::from_template(string name) //create a new game (in 'name' directory) from template { int i; char *ptr; struct stat buf; char *cfilename; dir = name; make_source_dir(); //check if template directory contains *dir files and vol.0 for(i=0;i<5;i++){ sprintf(tmp,"%s/%s",templatedir.c_str(),files[i]); if(stat(tmp,&buf)){ menu->errmes("AGI Studio error", "Can't read %s in template directory %s !",files[i],templatedir.c_str()); return 1; } } sprintf(tmp,"%s/*",templatedir.c_str()); #ifdef _WIN32 struct _finddata_t c_file; long hFile; if ((hFile = _findfirst(tmp, &c_file)) != -1L) do { sprintf(tmp,"%s/%s",templatedir.c_str(),c_file.name); cfilename = tmp; #else glob_t globbuf; glob(tmp, 0, NULL, &globbuf); for(i=0;i<(int)globbuf.gl_pathc;i++){ //copy template game files cfilename = globbuf.gl_pathv[i]; #endif ptr=strrchr(cfilename,'/'); if(ptr){ if(!strcmp(ptr,"/src"))continue; if(!strcmp(ptr,"/."))continue; if(!strcmp(ptr,"/.."))continue; sprintf(tmp,"%s%s",name.c_str(),ptr); } else{ if(!strcmp(cfilename,"/src"))continue; if(!strcmp(cfilename,"/."))continue; if(!strcmp(cfilename,"/.."))continue; sprintf(tmp,"%s/%s",name.c_str(),cfilename); } if(copy(cfilename,tmp))return 1; #ifdef _WIN32 } while (_findnext(hFile, &c_file) == 0); _findclose(hFile); #else } globfree(&globbuf); #endif sprintf(tmp,"%s/src/*",templatedir.c_str()); #ifdef _WIN32 if ((hFile = _findfirst(tmp, &c_file)) != -1L) do { sprintf(tmp,"%s/src/%s",templatedir.c_str(),c_file.name); cfilename = tmp; #else glob(tmp, 0, NULL, &globbuf); for(i=0;i<(int)globbuf.gl_pathc;i++){ //copy template src subdirectory cfilename = globbuf.gl_pathv[i]; #endif ptr=strrchr(cfilename,'/'); if(ptr){ if(!strcmp(ptr,"/."))continue; if(!strcmp(ptr,"/.."))continue; sprintf(tmp,"%s%s",srcdir.c_str(),ptr); } else{ if(!strcmp(cfilename,"/."))continue; if(!strcmp(cfilename,"/.."))continue; sprintf(tmp,"%s/%s",srcdir.c_str(),cfilename); } if(copy(cfilename,tmp))return 1; #ifdef _WIN32 } while (_findnext(hFile, &c_file) == 0); _findclose(hFile); #else } globfree(&globbuf); #endif return open(name); } //******************************************* void Game::make_source_dir() //create directory for sources (extracting logic, etc) { if(reldir) srcdir=dir+"/"+srcdirname; //srcdir is inside the game directory else srcdir=srcdirname; //srcdir can be anywhere #ifdef _WIN32 int ret=_mkdir(srcdir.c_str()); #else int ret=mkdir(srcdir.c_str(),0xfff); #endif if(ret==-1 && errno != EEXIST){ menu->errmes("Can't create the source directory %s !\nlogic text files will not be saved.",srcdirname.c_str()); } } //******************************************* int Game::newgame(string name) //create an empty game in 'name' directory { static byte BlankObjectFile[8] = {0x42,0x76,0x79,0x70,0x20,0x44,0x4A,0x72}; static byte BlankWordsFile [72] = { 0x00,0x34,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x9E,0x00,0x00,0x01,0x11,0x06,0x08,0x10,0x0D,0x9B,0x00, 0x01,0x00,0x0D,0x10,0x93,0x27,0x0F,0x00}; static const char* files[5] = {"vol.0","viewdir","logdir","snddir","picdir"}; FILE *fptr; int i,j; dir = name; for(i=0;i<5;i++){ sprintf(tmp,"%s/%s",name.c_str(),files[i]); fptr=fopen(tmp,"wb"); //create empty game files if(fptr==NULL){ menu->errmes("Can't create file %s !",files[i]); return 1; } fclose(fptr); } sprintf(tmp,"%s/object",dir.c_str()); fptr=fopen(tmp,"wb"); fwrite(BlankObjectFile,8,1,fptr); fclose(fptr); sprintf(tmp,"%s/words.tok",dir.c_str()); fptr=fopen(tmp,"wb"); fwrite(BlankWordsFile,72,1,fptr); fclose(fptr); make_source_dir(); isOpen = true; isV3 = false; ID = ""; AGIVersionNumber = 2.917; for(int _i = 0;_i <= 3;_i++){ for(j = 0;j <= 255; j++){ ResourceInfo[_i][j].Exists = false; } } menu->status->message(dir.c_str()); return 0; } //******************************************* string Game::FindAGIV3GameID(const char *name) //compare the prefix for vol.0 and dir - if they are same and non-NULL //it is a V3 game { string ID1; char *ptr; char *cfilename; ID1 = "V2"; //default for V2 games #ifdef _WIN32 struct _finddata_t c_file; long hFile; #else glob_t globbuf; #endif char dirString[10]="", volString[10]=""; sprintf(tmp,"%s/*dir",name); #ifdef _WIN32 if ((hFile = _findfirst(tmp, &c_file)) == -1L) { #else if (glob (tmp, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf)) { globfree(&globbuf); #endif return ID1; } #ifdef _WIN32 cfilename = c_file.name; #else cfilename = globbuf.gl_pathv[0]; #endif if((ptr=strrchr(cfilename,'/')))ptr++; else ptr=cfilename; strncpy (dirString, ptr, strlen (ptr) - 3); #ifdef _WIN32 _findclose(hFile); #else globfree(&globbuf); #endif sprintf(tmp,"%s/*vol.0",name); #ifdef _WIN32 if ((hFile = _findfirst(tmp, &c_file)) == -1L) { #else if (glob (tmp, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf)) { globfree(&globbuf); #endif return ID1; } #ifdef _WIN32 cfilename = c_file.name; #else cfilename = globbuf.gl_pathv[0]; #endif if((ptr=strrchr(cfilename,'/')))ptr++; else ptr=cfilename; strncpy (volString, ptr, strlen (ptr) - 5); #ifdef _WIN32 _findclose(hFile); #else globfree(&globbuf); #endif if ((strcmp(volString, dirString) == 0) && (volString != NULL)) ID1=volString; return ID1; } //******************************************************* double Game::GetAGIVersionNumber(void) { double VerNum; int ResPos,x; byte VerLen; bool ISVerNum; char VerNumText[16]; string InFileName = dir + "/agidata.ovl"; char VersionNumBuffer[] = "A_CDE_GHI"; double ret = 2.917; // This is what we use if we can't find the version number. // Version 2.917 is the most common interpreter and // the one that all the "new" AGI games should be based on. ResourceData.Size = 0; FILE *fptr=fopen(InFileName.c_str(),"rb"); if(fptr!=NULL){ struct stat buf; fstat(fileno(fptr),&buf); int size=buf.st_size; if(size < MaxResourceSize){ ResourceData.Size = size; fread(ResourceData.Data,ResourceData.Size,1,fptr); } fclose(fptr); } if(ResourceData.Size > 0){ ResPos = 0; VerLen = 0; while(ResPos < ResourceData.Size && VerLen == 0){ memcpy(VersionNumBuffer,&ResourceData.Data[ResPos],9); ResPos++; ISVerNum = true; if (VersionNumBuffer[1]=='.'){ if(VersionNumBuffer[0]<'0'||VersionNumBuffer[2]>'9')ISVerNum = false; for(x=2;x<=4;x++){ if(VersionNumBuffer[x]<'0'||VersionNumBuffer[x]>'9')ISVerNum = false; } if (ISVerNum && (VersionNumBuffer[0]<='2')) VerLen=5; // 2.xxx format if(VersionNumBuffer[5] != '.') ISVerNum = false; for(x=6;x<=8;x++){ // 3.xxx.xxx format if(VersionNumBuffer[x]<'0'||VersionNumBuffer[x]>'9')ISVerNum = false; } if(ISVerNum)VerLen=9; } else ISVerNum = false; if (VerLen>0){ if (VersionNumBuffer[5] == '.'){ strcpy(VerNumText,VersionNumBuffer); strcpy(VerNumText+5,VersionNumBuffer+6); // remove second . } else{ strncpy(VerNumText,VersionNumBuffer,VerLen); VerNumText[VerLen]=0; } VerNum=atof(VerNumText); if(VerNum!=0){ ret = VerNum; break; } } } } return ret; } //*************************************** int Game::GetResourceSize(char ResType_c,int ResNum) { byte lsbyte,msbyte; int ResType = ResType_c; if(ResourceInfo[ResType][ResNum].Exists){ sprintf(tmp,"%s/%s",dir.c_str(),ResourceInfo[ResType][ResNum].Filename); FILE *fptr=fopen(tmp,"rb"); if(fptr!=NULL){ struct stat buf; fstat(fileno(fptr),&buf); int size=buf.st_size; if(size >= ResourceInfo[ResType][ResNum].Loc+5){ fseek(fptr,ResourceInfo[ResType][ResNum].Loc,SEEK_SET); fread(&lsbyte,1,1,fptr); fread(&msbyte,1,1,fptr); if(lsbyte == 0x12 && msbyte == 0x34){ fread(&lsbyte,1,1,fptr); fread(&lsbyte,1,1,fptr); fread(&msbyte,1,1,fptr); return (msbyte*256 + lsbyte); } } fclose(fptr); } } return -1; } //*************************************** int Game::ReadResource(char ResType_c, int ResNum) //read a resource number ResNum from the vol file { byte msbyte,lsbyte; int ResType = (int)ResType_c; if(isV3){ return ReadV3Resource(ResType,ResNum); } sprintf(tmp,"%s/%s",dir.c_str(),ResourceInfo[ResType][ResNum].Filename); FILE *fptr=fopen(tmp,"rb"); if(fptr==NULL){ menu->errmes("Error reading file %s/%s",dir.c_str(),ResourceInfo[ResType][ResNum].Filename); return 1; } struct stat buf; fstat(fileno(fptr),&buf); int size=buf.st_size; if(ResourceInfo[ResType][ResNum].Loc > size){ menu->errmes("Error: %s: Specified resource location is past end of file",ResourceInfo[ResType][ResNum].Filename); return 1; } fseek(fptr,ResourceInfo[ResType][ResNum].Loc,SEEK_SET); fread(&msbyte,1,1,fptr); fread(&lsbyte,1,1,fptr); if(!(msbyte==0x12 && lsbyte==0x34)){ menu->errmes("Error: %s: Resource signature not found",ResourceInfo[ResType][ResNum].Filename); return 1; } fseek(fptr,ResourceInfo[ResType][ResNum].Loc+3,SEEK_SET); fread(&lsbyte,1,1,fptr); fread(&msbyte,1,1,fptr); ResourceData.Size = msbyte * 256 + lsbyte; if(ResourceData.Size==0){ menu->errmes("Error: %s: Resource size 0 !",ResourceInfo[ResType][ResNum].Filename); return 1; } fread(ResourceData.Data, ResourceData.Size,1,fptr); fclose(fptr); return 0; } //*************************************** FILE * Game::OpenPatchVol(int PatchVol,int *filesize) { FILE *fptr; if(isV3) sprintf(tmp,"%s/%svol.%d",dir.c_str(),ID.c_str(),PatchVol); else sprintf(tmp,"%s/vol.%d",dir.c_str(),PatchVol); fptr = fopen(tmp,"a+b"); if(fptr==NULL)return NULL; struct stat buf; fstat(fileno(fptr),&buf); *filesize=buf.st_size; return fptr; } //*********************************************** static int RewriteDirFile(FILE *dir,int dirsize) //used for V3 games { byte DirData[4][768]; int CurResType; byte lsbyte,msbyte; int Offset[4],Size[4]; byte OffsetArray[8]; fseek(dir,0,SEEK_SET); for(CurResType=3;CurResType>=0;CurResType--){ fseek(dir,CurResType*2,SEEK_SET); fread(&lsbyte,1,1,dir); fread(&msbyte,1,1,dir); Offset[CurResType]=(msbyte<<8)|lsbyte; if(CurResType == 3) Size[CurResType]=dirsize-Offset[CurResType]; else Size[CurResType]=Offset[CurResType+1]-Offset[CurResType]; if(Offset[CurResType]>dirsize || Size[CurResType]>768 || Offset[CurResType]+Size[CurResType]>dirsize){ menu->errmes("DIR file is invalid"); fclose(dir); return 1; } } for(CurResType =0;CurResType<=3;CurResType++){ fseek(dir,Offset[CurResType],SEEK_SET); memset(DirData[CurResType],0xff,768); fread(DirData[CurResType],Size[CurResType],1,dir); } OffsetArray[0] = 8; OffsetArray[1] = 0; Offset[0] = 8; for(CurResType =1;CurResType<=3;CurResType++){ Offset[CurResType] = Offset[CurResType-1] + 768; OffsetArray[CurResType*2] = Offset[CurResType] % 256; OffsetArray[CurResType*2+1] = Offset[CurResType] / 256; } fseek(dir,0,SEEK_SET); fwrite(OffsetArray,8,1,dir); for(CurResType =0;CurResType<=3;CurResType++){ fwrite(DirData[CurResType],768,1,dir); } fflush(dir); return 0; } //*********************************************** FILE * Game::OpenDirUpdate(int *dirsize,int ResType) { FILE *fptr; if(isV3) dirname = dir + "/" + ID + "dir"; else dirname = dir + "/" + ResTypeAbbrv[ResType] + "dir"; fptr = fopen(dirname.c_str(),"r+b"); if(fptr==NULL){ menu->errmes("Error writing file %s ! ",dirname.c_str()); return NULL; } struct stat buf; fstat(fileno(fptr),&buf); *dirsize=buf.st_size; return fptr; } //*********************************************** int Game::AddResource(int ResType,int ResNum) { FILE *fptr,*dirf; int filesize,dirsize; int PatchVol; byte ResHeader[7],DirByte[3]; int off; byte lsbyte,msbyte; if((dirf = OpenDirUpdate(&dirsize,ResType))==NULL)return 1; PatchVol = 0; fptr = OpenPatchVol(PatchVol,&filesize); //open vol.0 if(fptr==NULL){ menu->errmes("Can't open vol.%d !",PatchVol); return 1; } do{ if(filesize + ResourceData.Size > 1048000){ //current volume is too big (to fit a diskette) - create the next one fclose(fptr); PatchVol++; fptr=OpenPatchVol(PatchVol,&filesize); if(fptr==NULL){ if(isV3) menu->errmes("Can't open %svol.%d !",ID.c_str(),PatchVol); else menu->errmes("Can't open vol.%d !",PatchVol); return 1; } } }while(filesize + ResourceData.Size > 1048000); //write the resource to the patch volume and update the DIR file if(isV3){ if(RewriteDirFile(dirf,dirsize)){ fclose(dirf); fclose(fptr); return 1; } } else{ if(ResNum*3 > dirsize){ DirByte[1] = 0xff; fseek(dirf,dirsize,SEEK_SET); do{ fwrite(&DirByte[1],1,1,dirf); }while(ftell(dirf)!=ResNum*3); } } fseek(fptr,filesize,SEEK_SET); off = ftell(fptr); DirByte[0] = PatchVol*0x10 + off / 0x10000; DirByte[1] = (off % 0x10000) / 0x100; DirByte[2] = off % 0x100; ResourceInfo[ResType][ResNum].Exists = true; ResourceInfo[ResType][ResNum].Loc = off; if(isV3)sprintf(ResourceInfo[ResType][ResNum].Filename,"%svol.%d",ID.c_str(),PatchVol); else sprintf(ResourceInfo[ResType][ResNum].Filename,"vol.%d",PatchVol); int n=0; ResHeader[n++] = 0x12; ResHeader[n++] = 0x34; ResHeader[n++] = PatchVol; ResHeader[n++] = ResourceData.Size % 256; ResHeader[n++] = ResourceData.Size / 256; if(isV3){ ResHeader[n++] = ResourceData.Size % 256; // no compression so compressed size ResHeader[n++] = ResourceData.Size / 256; // and uncompressed size are the same } fwrite(ResHeader,n,1,fptr); fwrite(ResourceData.Data,ResourceData.Size,1,fptr); if(isV3){ fseek(dirf,ResType*2,SEEK_SET); fread(&lsbyte,1,1,dirf); fread(&msbyte,1,1,dirf); off = (msbyte<<8)|lsbyte; fseek(dirf,off+ResNum*3,SEEK_SET); fwrite(DirByte,3,1,dirf); fclose(dirf); } else{ fseek(dirf,ResNum*3,SEEK_SET); fwrite(DirByte,3,1,dirf); fclose(dirf); } fflush(fptr); return 0; } //************************************************ int Game::DeleteResource(int ResType,int ResNum) { FILE *dirf; int dirsize; byte lsbyte,msbyte; int off; if((dirf = OpenDirUpdate(&dirsize,ResType))==NULL)return 1; if(isV3){ if(dirsize<8){ menu->errmes("Error: %s file invalid!",dirname.c_str()); fclose(dirf); return 1; } fseek(dirf,ResType*2,SEEK_SET); fread(&lsbyte,1,1,dirf); fread(&msbyte,1,1,dirf); off = (msbyte<<8)|lsbyte; if(dirsize < off+ResNum*3+2){ menu->errmes("Error: %s file invalid!",dirname.c_str()); fclose(dirf); return 1; } fseek(dirf,off+ResNum*3,SEEK_SET); } else{ if(dirsize < ResNum*3 + 2){ menu->errmes("Error: %s file invalid!",dirname.c_str()); fclose(dirf); return 1; } fseek(dirf,ResNum*3,SEEK_SET); } byte b = 0xff; fwrite(&b,1,1,dirf); fwrite(&b,1,1,dirf); fwrite(&b,1,1,dirf); ResourceInfo[ResType][ResNum].Exists = false; fclose(dirf); return 0; } //************************************************ int Game::RebuildVOLfiles() { int step=0,steps=0; int ResType,ResNum,VolFileNum; byte b=0xff,byte1,byte2,byte3; FILE *dirf=NULL,*fptr; byte ResHeader[7]; long off; #define MaxVOLFileSize 1023*1024 TResourceInfo NewResourceInfo[4][256]; int ResourceNum[4]; bool cancel=false; char tmp1[1024]; int DirOffset[4]; #ifdef _WIN32 struct _finddata_t c_file; long hFile; #endif char volname[8]="vol"; if(isV3){ sprintf(volname,"%svol",ID.c_str()); } for(ResType = 0; ResType <= 3;ResType++){ ResourceNum[ResType]=0; for(ResNum=0;ResNum<256;ResNum++){ if(ResourceInfo[ResType][ResNum].Exists){ steps++; //number of steps for the progress bar (if necessary) ResourceNum[ResType]=ResNum; } } } Q3ProgressDialog progress( "Rebuilding VOL files...", "Cancel", steps,0, 0, TRUE ); //shows up if the operation is taking more than 3 sec //(so it never shows up...) ResHeader[0]=0x12; ResHeader[1]=0x34; VolFileNum=0; sprintf(tmp,"%s/%s.%d.new",dir.c_str(),volname,VolFileNum); if((fptr=fopen(tmp,"wb"))==NULL){ menu->errmes("Error creating file %s ! ",tmp); progress.cancel(); return 1; } if(isV3){ sprintf(tmp,"%s/%sdir.new",dir.c_str(), ID.c_str()); if((dirf=fopen(tmp,"wb"))==NULL){ menu->errmes("Error creating file %s ! ",tmp); progress.cancel(); return 1; } for (ResType = 0;ResType<=3;ResType++){ DirOffset[ResType] = 8 + ResType*0x300; byte1 = DirOffset[ResType] % 0x100; byte2 = DirOffset[ResType] / 0x100; fwrite(&byte1,1,1,dirf); fwrite(&byte2,1,1,dirf); } for(ResType = 0; ResType <= 3;ResType++){ for(ResNum=0;ResNum<256;ResNum++){ fwrite(&b,1,1,dirf); fwrite(&b,1,1,dirf); fwrite(&b,1,1,dirf); } } fflush(dirf); fseek(dirf,0,SEEK_SET); } for(ResType = 0; ResType <= 3;ResType++){ if(!isV3){ sprintf(tmp,"%s/%sdir.new",dir.c_str(), ResTypeAbbrv[ResType]); if((dirf=fopen(tmp,"wb"))==NULL){ menu->errmes("Error creating file %s !",tmp); progress.cancel(); return 1; } for(ResNum=0;ResNumerrmes("Error saving %s.%d !",ResTypeAbbrv[ResType],ResNum); progress.cancel(); return 1; } off=ftell(fptr); if(off + ResourceData.Size + 5 > MaxVOLFileSize){ fclose(fptr); VolFileNum++; sprintf(tmp,"%s/%s.%d.new",dir.c_str(),volname,VolFileNum); if((fptr=fopen(tmp,"wb"))==NULL){ menu->errmes("Error creating file %s !",tmp); progress.cancel(); return 1; } off=ftell(fptr); } NewResourceInfo[ResType][ResNum].Exists = true; NewResourceInfo[ResType][ResNum].Loc = off; sprintf(NewResourceInfo[ResType][ResNum].Filename,"%s.%d",volname,VolFileNum); byte1 = VolFileNum*0x10 + off / 0x10000; byte2 = (off % 0x10000) / 0x100; byte3 = off % 0x100; ResHeader[2] = VolFileNum; ResHeader[3] = ResourceData.Size % 256; ResHeader[4] = ResourceData.Size / 256; if(isV3){ ResHeader[5] = ResourceData.Size % 256; ResHeader[6] = ResourceData.Size / 256; fwrite(ResHeader,7,1,fptr); } else{ fwrite(ResHeader,5,1,fptr); } fwrite(ResourceData.Data,ResourceData.Size,1,fptr); if(isV3) fseek(dirf,DirOffset[ResType]+ResNum*3,SEEK_SET); else fseek(dirf,ResNum*3,SEEK_SET); fwrite(&byte1,1,1,dirf); fwrite(&byte2,1,1,dirf); fwrite(&byte3,1,1,dirf); progress.setProgress( step++ ); if ( progress.wasCancelled() ){ cancel=true; break; } } if(!isV3) fclose(dirf); if ( progress.wasCancelled() ){ cancel=true; break; } } progress.setProgress( steps ); fclose(fptr); if(isV3)fclose(dirf); if(cancel)return 1; //cleanup temporary files if(isV3){ sprintf(tmp,"%s/%sdir.new",dir.c_str(), ID.c_str()); sprintf(tmp1,"%s/%sdir",dir.c_str(), ID.c_str()); rename(tmp,tmp1); } else{ for(ResType = 0; ResType <= 3;ResType++){ sprintf(tmp,"%s/%sdir.new",dir.c_str(), ResTypeAbbrv[ResType]); sprintf(tmp1,"%s/%sdir",dir.c_str(), ResTypeAbbrv[ResType]); rename(tmp,tmp1); } } QDir d( dir.c_str()); // Delete old VOLs... QStringList list = d.entryList( QString(volname) + ".?;" + QString(volname) + ".??" ); for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) d.remove( *it ); // ...and replace them with the new ones: list = d.entryList( QString(volname) + ".*.new" ); for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) { QString new_name = *it; new_name.replace(".new", ""); d.rename( *it, new_name ); } memcpy(ResourceInfo,NewResourceInfo,sizeof(ResourceInfo)); QMessageBox::information( menu, "AGI studio","Rebuilding is complete !"); return 0; } //*********************************************** //V3 decompression code from QT AGI Utilities static void initLZW() { if(prefix_code==NULL){ prefix_code = (unsigned int *)malloc(TABLE_SIZE*sizeof(unsigned int)); append_character = (byte *)malloc(TABLE_SIZE*sizeof(byte)); } } static void resetLZW() { input_bit_count=0; input_bit_buffer=0L; } /*************************************************************************** ** setBITS ** ** Purpose: To adjust the number of bits used to store codes to the value ** passed in. ***************************************************************************/ static int setBITS(int value) { if (value == MAXBITS) return TRUE; BITS = value; MAX_VALUE = (1 << BITS) - 1; MAX_CODE = MAX_VALUE - 1; return FALSE; } /*************************************************************************** ** decode_string ** ** Purpose: To return the string that the code taken from the input buffer ** represents. The string is returned as a stack, i.e. the characters are ** in reverse order. ***************************************************************************/ static byte *decode_string(byte *buffer,unsigned int code) { int i; i=0; while (code > 255) { *buffer++ = append_character[code]; code=prefix_code[code]; if (i++>=4000) { menu->errmes("Fatal error during code expansion !"); return NULL; } } *buffer=code; return(buffer); } /*************************************************************************** ** input_code ** ** Purpose: To return the next code from the input buffer. ***************************************************************************/ static unsigned int input_code(byte **input) { unsigned int return_value; while (input_bit_count <= 24) { input_bit_buffer |= (unsigned long) *(*input)++ << input_bit_count; input_bit_count += 8; } return_value = (input_bit_buffer & 0x7FFF) % (1 << BITS); input_bit_buffer >>= BITS; input_bit_count -= BITS; return(return_value); } /*************************************************************************** ** expand ** ** Purpose: To uncompress the data contained in the input buffer and store ** the result in the output buffer. The fileLength parameter says how ** many bytes to uncompress. The compression itself is a form of LZW that ** adjusts the number of bits that it represents its codes in as it fills ** up the available codes. Two codes have special meaning: ** ** code 256 = start over ** code 257 = end of data ***************************************************************************/ static void expand(byte *input, byte *output, int fileLength) { int next_code, new_code, old_code; int character /* counter=0, index, */ /*, i */; byte *string, *endAddr; setBITS(START_BITS); /* Starts at 9-bits */ next_code = 257; /* Next available code to define */ endAddr = (byte *)((long)output + (long)fileLength); old_code = input_code(&input); /* Read in the first code */ character = old_code; new_code = input_code(&input); while ((output < endAddr) && (new_code != 0x101)) { if (new_code == 0x100) { /* Code to "start over" */ next_code = 258; setBITS(START_BITS); old_code = input_code(&input); character = old_code; *output++ = (char)character; new_code = input_code(&input); } else { if (new_code >= next_code) { /* Handles special LZW scenario */ *decode_stack = character; string = decode_string(decode_stack+1, old_code); } else string = decode_string(decode_stack, new_code); /* Reverse order of decoded string and store in output buffer. */ character = *string; while (string >= decode_stack) *output++ = *string--; if (next_code > MAX_CODE) setBITS(BITS + 1); prefix_code[next_code] = old_code; append_character[next_code] = character; next_code++; old_code = new_code; new_code = input_code(&input); } } } //*********************************************** static void convertLOG(byte *logBuf, int logLen) { int startPos, endPos, i, avisPos=0, numMessages; /* Find the start and end of the message section */ startPos = *logBuf + (*(logBuf+1))*256 + 2; numMessages = logBuf[startPos]; endPos = logBuf[startPos+1] + logBuf[startPos+2]*256; logBuf += (startPos + 3); startPos = (numMessages * 2) + 0; /* Encrypt the message section so that it compiles with AGIv2 */ for (i=startPos; i> 4) + ((oldData & 0x0F) << 4); else outData = data; if ((outData == 0xF0) || (outData == 0xF2)) { *out++ = outData; if (mode == NORMAL) { data = picBuf[bufPos++]; *out++ = (data & 0xF0) >> 4; mode = ALTERNATE; } else { *out++ = (data & 0x0F); mode = NORMAL; } } else *out++ = outData; oldData = data; } *outLen = int(out - outBuf); } //*********************************************** int Game::ReadV3Resource(char ResourceType1_c, int ResourceID1) { byte msbyte,lsbyte; bool ResourceIsPicture; byte VolNumByte; int ResourceType1 = (int)ResourceType1_c; if(CompressedResource.Data==NULL) CompressedResource.Data=(byte *)malloc(MaxResourceSize); sprintf(tmp,"%s/%s",dir.c_str(),ResourceInfo[ResourceType1][ResourceID1].Filename); FILE *fptr=fopen(tmp,"rb"); if(fptr==NULL){ menu->errmes("Error reading file %s/%s",dir.c_str(),ResourceInfo[ResourceType1][ResourceID1].Filename); return 1; } struct stat buf; fstat(fileno(fptr),&buf); int size=buf.st_size; if(ResourceInfo[ResourceType1][ResourceID1].Loc > size){ menu->errmes("Error: %s: Specified resource location is past end of file",ResourceInfo[ResourceType1][ResourceID1].Filename); return 1; } fseek(fptr,ResourceInfo[ResourceType1][ResourceID1].Loc,SEEK_SET); fread(&msbyte,1,1,fptr); fread(&lsbyte,1,1,fptr); if(!(msbyte==0x12 && lsbyte==0x34)){ menu->errmes("Error: %s: Resource signature not found",ResourceInfo[ResourceType1][ResourceID1].Filename); return 1; } fread(&VolNumByte,1,1,fptr); ResourceIsPicture = ((VolNumByte & 0x80) == 0x80); fread(&lsbyte,1,1,fptr); fread(&msbyte,1,1,fptr); ResourceData.Size = msbyte * 256 + lsbyte; fread(&lsbyte,1,1,fptr); fread(&msbyte,1,1,fptr); CompressedResource.Size = msbyte * 256 + lsbyte; fread(CompressedResource.Data,CompressedResource.Size,1,fptr); if(ResourceIsPicture){ DecompressPicture(CompressedResource.Data,ResourceData.Data,CompressedResource.Size,&ResourceData.Size); } else if(CompressedResource.Size != ResourceData.Size){ initLZW(); resetLZW(); expand(CompressedResource.Data,ResourceData.Data,ResourceData.Size); if (ResourceType1 == LOGIC) convertLOG( ResourceData.Data,ResourceData.Size); } else{ ResourceData.Size = CompressedResource.Size; memcpy( ResourceData.Data, CompressedResource.Data,ResourceData.Size); } return 0; } //********************************************************* void Game::defaults() { res_default=VIEW; save_logic_as_text=true; show_elses_as_gotos=false; show_all_messages=true; show_special_syntax=true; reldir=true; srcdirname="src"; #ifdef _WIN32 command="sarien -e -H 0 ./"; char abspath[256]; // absolute path to the program file _fullpath(abspath,_pgmptr,255); char *mydir = (char *)(malloc(strlen(abspath)+1)); // will store the program's directory strcpy(mydir,abspath); char *lastslash = 0; lastslash = strrchr(mydir,'\\'); if (!lastslash) lastslash = strrchr(mydir,'/'); *lastslash = '\0'; char templatedir_c[256]; char helpdir_c[256]; sprintf(templatedir_c,"%s/template",mydir); sprintf(helpdir_c,"%s/help",mydir); templatedir = templatedir_c; helpdir = helpdir_c; #else command="nagi ./ || sarien -e -H 0 ./"; templatedir="/usr/share/agistudio/template"; helpdir="/usr/share/agistudio/help"; #endif picstyle=P_ONE; } //********************************************************* void Game::read_settings() //read ~/.agistudio file { char *home; FILE *fptr; int n; char *ptr; #ifdef _WIN32 home = (char *)malloc(256); GetWindowsDirectoryA(home,256); #else home = getenv("HOME"); #endif if(!home)home=getenv("home"); if(!home)return; sprintf(tmp,"%s/.agistudio",home); fptr=fopen(tmp,"rb"); if(!fptr)return; while(fgets(tmp,MAX_TMP,fptr)!=NULL){ if((ptr=strchr(tmp,0x0a)))*ptr=0; if((ptr=strchr(tmp,0x0d)))*ptr=0; if(!strncmp(tmp,"res_default=",12)){ res_default=atoi(tmp+12); } else if(!strncmp(tmp,"save_logic_as_text=",20)){ n=atoi(tmp+20); save_logic_as_text=(n==1); } else if(!strncmp(tmp,"show_all_messages=",18)){ n=atoi(tmp+18); show_all_messages=(n==1); } else if(!strncmp(tmp,"show_elses_as_gotos=",20)){ n=atoi(tmp+20); show_elses_as_gotos=(n==1); } else if(!strncmp(tmp,"show_special_syntax=",20)){ n=atoi(tmp+20); show_special_syntax=(n==1); } else if(!strncmp(tmp,"reldir=",8)){ n=atoi(tmp+8); reldir=(n==1); } else if(!strncmp(tmp,"command=",8)){ command=string(tmp+8); } else if(!strncmp(tmp,"srcdirname=",11)){ srcdirname=string(tmp+11); } else if(!strncmp(tmp,"template=",9)){ templatedir=string(tmp+9); } else if(!strncmp(tmp,"help=",5)){ helpdir=string(tmp+5); } else if(!strncmp(tmp,"picstyle=",9)){ picstyle=atoi(tmp+9); } } fclose(fptr); } //********************************************************* void Game::save_settings() //save ~/.agistudio file { char *home; FILE *fptr; #ifdef _WIN32 home = (char *)malloc(256); GetWindowsDirectoryA(home,256); #else home = getenv("HOME"); #endif if(!home)home=getenv("home"); if(!home){ menu->errmes("Can't determine HOME environment variable !\nSettings were not saved."); return; } sprintf(tmp,"%s/.agistudio",home); fptr=fopen(tmp,"wb"); if(!fptr){ menu->errmes("Can't open file %s for writing !\nSettings were not saved.",tmp); return; } fprintf(fptr,"res_default=%d\n",res_default); fprintf(fptr,"save_logic_as_text=%d\n",save_logic_as_text); fprintf(fptr,"show_elses_as_gotos=%d\n",show_elses_as_gotos); fprintf(fptr,"show_all_messages=%d\n",show_all_messages); fprintf(fptr,"show_special_syntax=%d\n",show_special_syntax); fprintf(fptr,"reldir=%d\n",reldir); fprintf(fptr,"command=%s\n",command.c_str()); fprintf(fptr,"srcdirname=%s\n",srcdirname.c_str()); fprintf(fptr,"template=%s\n",templatedir.c_str()); fprintf(fptr,"help=%s\n",helpdir.c_str()); fprintf(fptr,"picstyle=%d\n",picstyle); fclose(fptr); } //************************************************ int Game::RecompileAll() { int i,ResNum,err; FILE *fptr; Logic logic; char tmp1[16],*ptr; extern TStringList InputLines; for(i=0;ifilename != ""){ winlist[i].w.t->save(); winlist[i].w.t->status->message(""); } } else if(winlist[i].type==LOGIC){ if(winlist[i].w.l->filename != ""){ winlist[i].w.l->save_logic(); winlist[i].w.l->status->message(""); } } } int step=0,steps=0; for(ResNum=0;ResNum<256;ResNum++){ if(game->ResourceInfo[LOGIC][ResNum].Exists){ steps++; } } Q3ProgressDialog progress( "Recompiling all logics...", "Cancel", steps,0, 0, TRUE ); progress.setMinimumDuration(0); for(ResNum=0;ResNum<256;ResNum++){ if(game->ResourceInfo[LOGIC][ResNum].Exists){ //look for the source file first sprintf(tmp,"%s/logic.%03d",game->srcdir.c_str(),ResNum); fptr = fopen(tmp,"rb"); if(fptr==NULL){ sprintf(tmp,"%s/logic.%d",game->srcdir.c_str(),ResNum); fptr = fopen(tmp,"rb"); } if(fptr==NULL){ sprintf(tmp,"%s/logic%d.txt",game->srcdir.c_str(),ResNum); fptr = fopen(tmp,"rb"); } if(fptr!=NULL){ InputLines.lfree(); while(fgets(tmp,MAX_TMP,fptr)!=NULL){ if((ptr=strchr(tmp,0x0a)))*ptr=0; if((ptr=strchr(tmp,0x0d)))*ptr=0; //strcat(tmp,"\n"); InputLines.add(tmp); } fclose(fptr); } else{ //source file not found - reading from the game err=logic.decode(ResNum); if(err){ sprintf(tmp,"logic.%d",ResNum); menu->errmes(tmp,"Errors:\n%s",logic.ErrorList.c_str()); continue; } InputLines.lfree(); string::size_type pos; string str=logic.OutputText; while((pos=str.find_first_of("\n"))!=string::npos){ InputLines.add(str.substr(0,pos)); str=str.substr(pos+1); } if(str!=""){ InputLines.add(str); } } err=logic.compile(); if(!err){ game->AddResource(LOGIC,ResNum); } else{ if(logic.ErrorList != ""){ sprintf(tmp1,"logic.%d",ResNum); menu->errmes(tmp1,"Errors:\n%s",logic.ErrorList.c_str()); } } progress.setProgress( step++ ); if ( progress.wasCancelled() )return 1; } } progress.setProgress( steps ); QMessageBox::information( menu, "AGI studio","Recompilation is complete !"); return 0; }