/*
 *  OBLIVION ABOMB DEFUSER - Ver.0.1α
 *  Copyright (C)2019 ETOKAMI
 *
 *  ライセンスはGPLv2です
 */
#define  _GNU_SOURCE
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>


/* マクロ */
#define   TO_UPPER(n)      ((n >= 'a' && n <= 'z') ? n-32 : n)
#define   LCtoI32(n)       (*(n+3)<<24|*(n+2)<<16|*(n+1)<<8|*n)
#define   LCtoI16(n)       (*(n+1)<<8|*n)


/* 設定値 */
#define   FILEPATH_LIMIT   1023		/* ファイルパスの制限値 */
#define   FILENAME_LIMIT   255		/* ファイル名の制限値   */


/* 変更禁止 */
#define   APP_NAME         "OBLIVION ABOMB DEFUSER"
#define   APP_VERSION      "0.1α"
#define   APP_COPYRIGHT    "Copyright (C)2019 ETOKAMI"
#ifndef   APP_COMMAND
#define   APP_COMMAND      "obadef"
#endif


typedef   unsigned char    Uchar;


/* ファイルヘッダ用構造体 */
typedef struct {

  char    fileId[12];
  uint8_t majorVersion;
  uint8_t minorVersion;
  uint8_t exeTime[16];

} fileheader_t;


/* セーブファイルヘッダ用構造体 */
typedef struct {

  uint32_t headerVersion;
  uint32_t saveHeaderSize;
  uint32_t saveNum;
  uint8_t  pcName[256];
  uint16_t pcLevel;
  uint8_t  pcLocation[256];
  float    gameDays;
  uint32_t gameTicks;
  uint8_t  gameTime[16];
  uint32_t screenshotSize;
  uint32_t screenshotWidth;
  uint32_t screenshotHeight;

  uint32_t startAddress;

} saveheader_t;


typedef struct {

  uint8_t  pluginsNum;
  uint32_t startAddress;

} plugins_t;


/* Global用構造体 */
typedef struct {

  uint32_t formIdsOffset;
  uint32_t recordsNum;
  uint32_t nextObjectid;
  uint32_t worldId;
  uint32_t worldX;
  uint32_t worldY;
  uint32_t pcLocationCell;
  float    pcLocationX;
  float    pcLocationY;
  float    pcLocationZ;
  uint16_t globalsNum;
  uint16_t tesClassSize;
  uint32_t numDeathCounts;

  float    gameModeSeconds;
  uint32_t gameModeSecondsInt;

  uint32_t startAddress;

} global_t;


/* ４バイトcharを変換するための共用体 */
typedef union {

  uint32_t cvint;
  float    cvfloat;

} conv_t;


/* エラーメッセージ用 */
enum {

  DEBUG_MESSAGE          ,
  CANCEL                 ,
  HEAP_ALLOCATION_FAILED ,
  FILE_OPEN_ERROR        ,
  FILE_SEEK_ERROR        ,
  FILE_WRITE_ERROR       ,
  FILE_READ_ERROR        ,
  FILE_CLOSE_ERROR       ,
  FILE_NOT_FOUND         ,
  FILE_ACCESS_DENIED     ,
  FILE_WRITE_PROTECTED   ,
  NOT_OBLIVION_SAVE_FILE ,
  FILEPATH_TOO_LONG      ,
  FILENAME_TOO_LONG

};


/* プロトタイプ宣言 */
void   get_filestatus(size_t *,uint8_t *,const char *);
int    read_header(FILE *,uint32_t *,fileheader_t *);
int    read_saveheader(FILE *,uint32_t *,saveheader_t *);
int    read_plugins(FILE *,uint32_t *,plugins_t *);
int    read_global(FILE *,uint32_t *,global_t *);
int    yes_no_pillows(int);
char * timetostring(uint8_t *);
void   show_headerlist(fileheader_t *,saveheader_t *,plugins_t *,global_t *);
void   show_errormessage(int);
void   show_help(void);



/*
 *  メイン関数
 */
int main(int argc,char *argv[]) {

char filepath[FILEPATH_LIMIT+1] = {0};

FILE *fp   = NULL;
int  debug = 0;
int  err   = 0;

{
  /* コマンドオプション解析 */
  int  fn   = 0;
  char help = 0;
  char ct   = 0;

  if (argc < 2) { help = 1; }
           else { ct   = (argc >= 3) ? 3 : 2; }

  for (int i=1 ; i<ct ; i++) {
      if (fn == 0 && argv[i][0] != '-') { fn = i; }

      if (strcmp(argv[i],"-h") == 0
       || strcmp(argv[i],"--help" ) == 0) { help = 1; }

      /* デバッグ用オプション         *
       * この機能は非サポート扱いです */
      if (strcmp(argv[i],"-d") == 0
       || strcmp(argv[i],"--debug") == 0) { debug = 1; }
  }

  /* ファイル名未指定かヘルプ指定でヘルプ表示 */
  if (fn == 0 || help == 1) { show_help();
                              return 0; }


  /* ファイル名を設定 */
  if (strlen(argv[fn]) > FILEPATH_LIMIT)
      { err = FILEPATH_TOO_LONG;
        goto ERROR; }

  strcpy(filepath,argv[fn]);

  char *p = filepath;
  char *n = basename(p);
  if (strlen(n) > FILENAME_LIMIT)
      { err = FILENAME_TOO_LONG;
        goto ERROR; }
}


fileheader_t fileheader = {0};
saveheader_t saveheader = {0};
plugins_t    plugins    = {0};
global_t     global     = {0};


/* ファイルの状態をチェック */
{
  size_t  filesize;
  uint8_t status;
  get_filestatus(&filesize,&status,filepath);
  err = ( status    == 0xff) ? FILE_NOT_FOUND
      : ((status&1) !=    0) ? FILE_ACCESS_DENIED
      : ((status&2) !=    0) ? FILE_WRITE_PROTECTED
      : ((status&0xf0) != 0) ? NOT_OBLIVION_SAVE_FILE : 0;

  if (err) { goto ERROR; }

}


if ((fp = fopen(filepath,"r+b")) == NULL)
    { err = FILE_OPEN_ERROR;
      goto ERROR; }


/* セーブファイルの物理アドレス                   *
 * 最終的にABOMB(gameModeSeconds)の位置を示します */
uint32_t address = 0;


/* ファイルヘッダを読み込む */
if ((err = read_header(fp,&address,&fileheader)))
    { goto ERROR; }


/* セーブファイルヘッダを読み込む */
if ((err = read_saveheader(fp,&address,&saveheader)))
    { goto ERROR; }


/* Pluginsを読み込む */
if ((err = read_plugins(fp,&address,&plugins)))
    { goto ERROR; }


/* Globalを読み込む */
if ((err = read_global(fp,&address,&global)))
    { goto ERROR; }


if (debug) {

    show_headerlist(&fileheader,&saveheader,&plugins,&global);

} else {

    /* セーブデータの状態を出力 */
    printf("SavefileVer:%d.%d\n",fileheader.majorVersion,fileheader.minorVersion);
    printf("PlayerName :%s\n",saveheader.pcName);
    printf("PlayerLevel:%d\n",saveheader.pcLevel);
    printf("Location   :%s\n",saveheader.pcLocation);
    printf("ABOMB      :0x%08x(%f)\n",global.gameModeSecondsInt,global.gameModeSeconds);
    printf("Address    :0x%08x\n",address);

}


/* 書き換え確認 */
printf("\nABOMB(gameModeSeconds)を書き換えてもよろしいですか？[y/N or ENTER]");

if (yes_no_pillows(1)) { err = CANCEL; goto ERROR; }


/* ABOMB(gameModeSeconds)の位置にシーク */
if (fseek(fp,address,SEEK_SET))
    { err = FILE_SEEK_ERROR;
      goto ERROR; }


/* ABOMB(gameModeSeconds)を初期値8.0(0x41000000)に戻す */
{
  uint8_t fix[4] = { 0,0,0,0x41 };

  for (int i=0 ; i<4 ; i++) {
      if (fputc(fix[i],fp) == EOF)
          { err = FILE_WRITE_ERROR;
            break; }
  }
}


ERROR:


if (fp != NULL) {
    if (fclose(fp) == 0) { fp  = NULL; }
                    else { err = FILE_CLOSE_ERROR; }
}


if (err == 0) { puts("正常終了しました\n"); }
         else { if (err > CANCEL) { show_errormessage(err); }
                puts("中断しました\n"); }

return 0;


}



/*
 *  get_filestatus( d0 , d1 , s0 )
 *  引数  ： s0 調べるファイル名
 *  戻り値： d0 ファイルサイズ
 *           d1 ファイルの状態(ファイルが存在しない場合は0xff)
 *
 *  statとlstat、accessを使用してファイル状態を調べるUnix系依存関数です
 *  statusはビットマスクして判定してください
 *  bit0 -> 0:読み込み可能 / 1:不可
 *  bit1 -> 0:書き込み可能 / 1:不可
 *  bit2 -> 0:    実行可能 / 1:不可
 *  bit3 -> 1:シンボリックリンク
 *  bit4 -> 1:ディレクトリ
 *  bit5 -> 1:キャラクタファイル
 *  bit6 -> 1:ブロックファイル
 *  bit7 -> 1:FIFO
 */
void get_filestatus(size_t *filesize,uint8_t *status,const char *filepath) {

*status = 0;

struct stat stbuf;
struct stat lsbuf;
if ( stat(filepath,&stbuf) == -1) { *status = 0xff; return; }
if (lstat(filepath,&lsbuf) == -1) { *status = 0xff; return; }

*filesize   = stbuf.st_size;

mode_t st_mode = stbuf.st_mode;

uint8_t st = 0;

st  = (S_ISDIR(st_mode) ) ?  16 : 0;
st |= (S_ISCHR(st_mode) ) ?  32 : 0;
st |= (S_ISBLK(st_mode) ) ?  64 : 0;
st |= (S_ISFIFO(st_mode)) ? 128 : 0;

st |= (access(filepath,R_OK)) ? 1 : 0;
st |= (access(filepath,W_OK)) ? 2 : 0;
st |= (access(filepath,X_OK)) ? 4 : 0;

st |= (S_ISLNK(lsbuf.st_mode)) ? 8 : 0;

*status = st;


}



/*
 *  read_header( s0 , fileheader_t )
 *  引数  ： s0 ファイルディスクリプタ
 *  戻り値：  0 正常終了
 *           1- エラーコード
 *
 *  ファイルヘッダを読み込み指定した構造体に格納します
 */
int read_header(FILE *fp,uint32_t *address,fileheader_t *fileheader) {

size_t readsize = 30;
Uchar  buf[readsize];

/* ファイルヘッダ30バイトを読み込み */
if (fread(buf,1,readsize,fp) < readsize)
    { return FILE_READ_ERROR; }


uint32_t addr = 0;


/* fileId取得 */
{
  char *p   = fileheader->fileId;
  for ( ; addr<12 ; addr++) { *p++ = buf[addr]; }
}


/* セーブファイルバージョン取得 */
fileheader->majorVersion = (uint8_t)buf[addr++];
fileheader->minorVersion = (uint8_t)buf[addr++];


/* extTime取得 */
{
  uint8_t *p = (uint8_t *)fileheader->exeTime;
  for ( ; addr<30 ; addr++) { *p++ = (uint8_t)buf[addr]; }
}


/* ファイルヘッダの文字列を検出できない時はエラー */
if (strncmp(fileheader->fileId,"TES4SAVEGAME",12) != 0)
    { return NOT_OBLIVION_SAVE_FILE; }


/* ファイルヘッダ分アドレスを移動 */
*address += 30;


return 0;


}



/*
 *  read_saveheader( s0 , s1 , saveheader_t )
 *  引数  ： s0 ファイルディスクリプタ
 *           s1 アドレス用変数のポインタ
 *  戻り値：  0 正常終了
 *           1- エラーコード
 *
 *  セーブファイルヘッダを読み込み後続のアドレスを求める処理です
 */
int read_saveheader(FILE *fp,uint32_t *address,saveheader_t *saveheader) {

saveheader->startAddress = *address;

conv_t conv;

size_t readsize = 1024;
Uchar  buf[readsize];

/* ヘッダを推定サイズで読み込む */
if (fseek(fp,*address,SEEK_SET))
    { return FILE_SEEK_ERROR; }
if (fread((char *)buf,1,readsize,fp) < readsize)
    { return FILE_READ_ERROR; }


/* リードバッファの物理アドレス */
uint32_t addr = 0;


/* headerVersion, saveHeaderSize, saveNum合計12バイトを取得 */
saveheader->headerVersion  = (uint32_t)LCtoI32(&buf[0]);
saveheader->saveHeaderSize = (uint32_t)LCtoI32(&buf[4]);
saveheader->saveNum        = (uint32_t)LCtoI32(&buf[8]);
addr += 12;


/* プレイヤーキャラクタ名を取得 */
uint8_t size = (uint8_t)buf[addr++];
if (size > 0) {
    uint8_t *p = (uint8_t *)saveheader->pcName;
    for (int i=0 ; i<size ; i++) { *p++ = buf[addr++]; }
}


/* プレイヤーキャラクタのレベルを取得 */
saveheader->pcLevel = (uint16_t)LCtoI16(&buf[addr]);
addr += 2;


/* プレイヤーキャラクタの現在位置を取得 */
size = (uint8_t)buf[addr++];
if (size > 0) {
    uint8_t *p = (uint8_t *)saveheader->pcLocation;
    for (int i=0 ; i<size ; i++) { *p++ = buf[addr++]; }
}


/* gameDays, gameTicks, gameTimeを取得 */
conv.cvint = (uint32_t)LCtoI32(&buf[addr]);
saveheader->gameDays = conv.cvfloat;
addr += 4;

saveheader->gameTicks = (uint32_t)LCtoI32(&buf[addr]);
addr += 4;

{
  uint8_t *p = saveheader->gameTime;
  for (int i=0 ; i<16 ; i++) { *p++ = (uint8_t)buf[addr++]; }
}


/* screenshot.size, width, heightを取得 *
 * ここでは画像自体は取得しません       */
saveheader->screenshotSize   = (uint32_t)LCtoI32(&buf[addr]);
addr += 4;
saveheader->screenshotWidth  = (uint32_t)LCtoI32(&buf[addr]);
saveheader->screenshotHeight = (uint32_t)LCtoI32(&buf[addr+4]);


/* screenshot.width, height分のバイト数を          *
 * screenshot.sizeが含んでいるため除いた分を進める */
addr += saveheader->screenshotSize;


/* ファイルの物理アドレスを更新 */
*address += addr;


return 0;


}



/*
 *  read_plugins( s0 , s1 )
 *  引数  ： s0 ファイルディスクリプタ
 *           s1 アドレス用変数のポインタ
 *  戻り値：  0 正常終了
 *           1- エラーコード
 *
 *  Plugins内のファイル名を走査して後続の位置を求める処理です
 *  ファイル名そのものは取得していません
 */
int read_plugins(FILE *fp,uint32_t *address,plugins_t *plugins) {

plugins->startAddress = *address;

/* リードバッファの物理アドレス */
uint32_t addr = 0;


/* Pluginsの理論値は最大255x256で65,280バイトの、はず。。。 */
size_t  readsize = 65536;
Uchar   buf[readsize];

if (fseek(fp,*address,SEEK_SET))
    { return FILE_SEEK_ERROR; }
if (fread(buf,sizeof(char),readsize,fp) < readsize)
    { return FILE_READ_ERROR; }


/* プラグイン総数を取得 */
plugins->pluginsNum = (uint8_t)buf[addr++];


/* プラグインを走査して最後尾を求めます *
 * ファイル名は取得していません         */
for (int i=0 ; i<plugins->pluginsNum ; i++) {
    /* 文字数(buf[addr])とbuf[addr]自身(+1)を加算 */
    addr += (buf[addr]+1);
}


/* ファイルの物理アドレスを更新 */
*address += addr;


return 0;


}



/*
 *  read_global( s0 , s1 , global_t )
 *  引数  ： s0 ファイルディスクリプタ
 *           s1 アドレス用変数のポインタ
 *  戻り値：  0 正常終了
 *           1- エラーコード
 *
 *  Global内のデータを読み込みます
 *  この中のTesClass内にABOMB(gameModeSeconds)があります
 */
int read_global(FILE *fp,uint32_t *address,global_t *global) {

global->startAddress = *address;

conv_t conv;

size_t  readsize = 42;
Uchar   buf[readsize];

/* Global先頭から42バイトを読み込み */
if (fseek(fp,*address,SEEK_SET))
    { return FILE_SEEK_ERROR; }
if (fread(buf,sizeof(char),readsize,fp) < readsize)
    { return FILE_READ_ERROR; }


/* リードバッファの物理アドレス */
uint32_t addr = 0;


/* formIdsOffset -> pcLocation.zまでの合計40バイトを取得 */
global->formIdsOffset  = (uint32_t)LCtoI32(&buf[ 0]);
global->recordsNum     = (uint32_t)LCtoI32(&buf[ 4]);
global->nextObjectid   = (uint32_t)LCtoI32(&buf[ 8]);
global->worldId        = (uint32_t)LCtoI32(&buf[12]);
global->worldX         = (uint32_t)LCtoI32(&buf[16]);
global->worldY         = (uint32_t)LCtoI32(&buf[20]);
global->pcLocationCell = (uint32_t)LCtoI32(&buf[24]);
conv.cvint = (uint32_t)LCtoI32(&buf[28]);
global->pcLocationX = conv.cvfloat;
conv.cvint = (uint32_t)LCtoI32(&buf[32]);
global->pcLocationY = conv.cvfloat;
conv.cvint = (uint32_t)LCtoI32(&buf[36]);
global->pcLocationZ = conv.cvfloat;

addr += 40;


/* globalsNum(40,41バイト目)を取得しglobalsをスキップする */
global->globalsNum = (uint16_t)LCtoI16(&buf[addr]);
addr += 2;


/* ファイルの物理アドレスを更新 */
*address += (global->globalsNum*8+addr);


/* globalsのサイズが不確定のためtesClass以降を６バイト再読込 */
readsize = 6;

if (fseek(fp,*address,SEEK_SET))
    { return FILE_SEEK_ERROR; }
if (fread(buf,sizeof(char),readsize,fp) < readsize)
    { return FILE_READ_ERROR; }

addr = 0;


/* tesClassSizeを取得 */
global->tesClassSize = (uint16_t)LCtoI16(&buf[addr]);
addr += 2;


/* numDeathCountsを取得 */
global->numDeathCounts = (uint32_t)LCtoI32(&buf[addr]);


/* 本ツールではdeathCountsは使用しないため丸ごとスキップ */


/* ABOMB(gameModeSeconds)の位置を特定 *
 * tesClassSize - 4 = gameModeSeconds */
addr += (global->tesClassSize-4);


/* ファイルの物理アドレスを更新 */
*address += addr;


/* ABOMB(gameModeSeconds)の数値を取得               *
 * 共用体で整数型と単精度浮動小数点に変換しています */
readsize = 4;

if (fseek(fp,*address,SEEK_SET))
    { return FILE_SEEK_ERROR; }
if (fread(buf,sizeof(char),readsize,fp) < readsize)
    { return FILE_READ_ERROR; }

conv.cvint = (uint32_t)LCtoI32(&buf[0]);

global->gameModeSeconds    = conv.cvfloat;
global->gameModeSecondsInt = conv.cvint;


return 0;


}



/*
 *  yes_no_pillows( s0 )
 *  引数  ： s0 エンターを押した時に返すコード
 *  戻り値： -- 押したキーの結果
 *
 *  キー入力待ち処理です
 *  引数が０か１の時はＹかＮが押されるまでキー入力を待ちます
 *  エンターを押すと引数が０の時はＹ、１の時はＮが押された扱いとなります
 *  戻り値はＹが押されるとゼロを、Ｎが押されると１を返します
 *
 *  関数名に深い意味はない(適切なのが思いつかなかっただけ)
 */
int yes_no_pillows(int defkey) {

system("stty -icanon -echo");
int chr,code;

while(1) {

    chr = getchar();

    chr = TO_UPPER(chr);

    if (chr == 10 ) { chr  = (defkey&1) ? 'N' : 'Y'; }
    if (chr == 'N') { code =  1; break; }
    if (chr == 'Y') { code =  0; break; }

}

putchar(chr);
printf("\n");

system("stty icanon echo");

return code;


}



/*
 *  d0 = timetostring( s0 )
 *  引数  ： s0 変換元変数のポインタ
 *  戻り値： d0 文字列を格納した配列のポインタ
 *
 *  Windowsのgetlocaltimeで得られた数値を文字列に変換する
 */
char * timetostring(uint8_t *exeTime) {

struct getlocaltime {
  uint16_t wYear;
  uint16_t wMonth;
  uint16_t wDayOfWeek;
  uint16_t wDay;
  uint16_t wHour;
  uint16_t wMinute;
  uint16_t wSecond;
  uint16_t wMilliSeconds;
};

struct getlocaltime systime;

char *dayofweek[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};

systime.wYear         = (uint16_t)LCtoI16(&exeTime[0]);
systime.wMonth        = (uint16_t)LCtoI16(&exeTime[2]);
systime.wDayOfWeek    = (uint16_t)LCtoI16(&exeTime[4])%7;
systime.wDay          = (uint16_t)LCtoI16(&exeTime[6]);
systime.wHour         = (uint16_t)LCtoI16(&exeTime[8]);
systime.wMinute       = (uint16_t)LCtoI16(&exeTime[10]);
systime.wSecond       = (uint16_t)LCtoI16(&exeTime[12]);
systime.wMilliSeconds = (uint16_t)LCtoI16(&exeTime[14]);

static char str[64] = {0};

snprintf(str,64,"%s, %02d/%02d/%04d %02d:%02d:%02d.%d"
    ,dayofweek[systime.wDayOfWeek]
    ,systime.wDay
    ,systime.wMonth
    ,systime.wYear
    ,systime.wHour
    ,systime.wMinute
    ,systime.wSecond
    ,systime.wMilliSeconds);


return str;


}



/*
 *  show_header( fileheader_t , saveheader_t , global_t )
 *
 *  セーブファイルの一部を画面出力する関数
 *  デバッグ用、この関数の動作は未サポート扱い
 */
void show_headerlist(fileheader_t *fileheader,saveheader_t *saveheader,plugins_t *plugins,global_t *global) {


char buf[17] = {0};

puts("* fileheader *");
strncpy(buf,fileheader->fileId,12);
printf("fileId         : %s\n",buf);
printf("fileVersion    : %d.%d\n",fileheader->majorVersion,fileheader->minorVersion);

char *str = timetostring(fileheader->exeTime);
printf("exeTime        : %s\n\n",str);

puts("* saveheader *");
printf("START ADDRESS  : 0x%08x\n",saveheader->startAddress);
printf("HeaderVersion  : %d\n",saveheader->headerVersion);
printf("saveHeaderSize : %d\n",saveheader->saveHeaderSize);
printf("saveNum        : %d\n",saveheader->saveNum);
printf("pcName         : %s\n",saveheader->pcName);
printf("pcLevel        : %d\n",saveheader->pcLevel);
printf("pcLocation     : %s\n",saveheader->pcLocation);
printf("gameDays       : %f\n",saveheader->gameDays);
printf("gameTicks      : %d\n",saveheader->gameTicks);

str = timetostring(saveheader->gameTime);
printf("gameTime       : %s\n",str);

printf("screenshot.size: %d\n",saveheader->screenshotSize);
printf("screenshot.w/h : %d , %d\n\n",saveheader->screenshotWidth,saveheader->screenshotHeight);


puts("* plugins *");
printf("START ADDRESS  : 0x%08x\n",plugins->startAddress);
printf("pluginsNum     : %d\n\n",plugins->pluginsNum);


puts("* global *");
printf("START ADDRESS  : 0x%08x\n",global->startAddress);
printf("formIdsOffset  : 0x%x\n",global->formIdsOffset);
printf("recordsNum     : %d\n",global->recordsNum);
printf("nextObjectid   : 0x%x\n",global->nextObjectid);
printf("worldId        : 0x%x\n",global->worldId);
printf("worldX         : 0x%x\n",global->worldX);
printf("worldY         : 0x%x\n",global->worldY);
printf("pcLocation.cell: 0x%x\n",global->pcLocationCell);
printf("pcLocation.X   : %f\n",global->pcLocationX);
printf("pcLocation.Y   : %f\n",global->pcLocationY);
printf("pcLocation.Z   : %f\n",global->pcLocationZ);
printf("globalsNum     : %d\n",global->globalsNum);
printf("tesClassSize   : %d\n",global->tesClassSize);
printf("numDeathCounts : %d\n",global->numDeathCounts);
printf("gameModeSeconds: 0x%x(%f)\n",global->gameModeSecondsInt,global->gameModeSeconds);


}



/*
 *  show_errormessage( s0 )
 *  引数： s0 エラーコード
 *
 *  エラーメッセージを出力します
 */
void show_errormessage(int code) {

switch (code) {

case DEBUG_MESSAGE:
     puts("デバッグメッセージ");
     break;
case HEAP_ALLOCATION_FAILED:
     puts("メモリの確保に失敗しました");
     break;
case FILE_OPEN_ERROR:
     puts("ファイルオープンに失敗しました");
     break;
case FILE_SEEK_ERROR:
     puts("ファイルシークに失敗しました");
     break;
case FILE_WRITE_ERROR:
     puts("ファイルへの書き込みに失敗しました");
     break;
case FILE_READ_ERROR:
     puts("ファイルの読み込みに失敗しました");
     break;
case FILE_CLOSE_ERROR:
     puts("ファイルクローズに失敗しました");
     break;
case FILE_NOT_FOUND:
     puts("指定したファイルは見つかりませんでした");
     break;
case FILE_ACCESS_DENIED:
     puts("ファイルへのアクセスは拒否されました");
     break;
case FILE_WRITE_PROTECTED:
     puts("ファイルは書き込み禁止になっています");
     break;
case NOT_OBLIVION_SAVE_FILE:
     puts("指定したファイルはセーブデータではありません");
     break;
case FILEPATH_TOO_LONG:
     puts("ファイルパスが長すぎます");
     break;
case FILENAME_TOO_LONG:
     puts("ファイル名が長すぎます");
     break;

}


}



/*
 *  show_help()
 *  ヘルプを出力します
 */
void show_help(void) {

puts(APP_NAME" Ver."APP_VERSION" - "APP_COPYRIGHT);
puts("使用法："APP_COMMAND" ファイル名.ess\n");
puts("OBLIVIONのABOMBカウンタ(gameModeSeconds)を直接書き換えて\n"
     "エフェクト遅延バグを解消します\n"
     "実行の前に＊必ず＊セーブデータのバックアップを取ってから\n"
     "行ってください\n");
puts("本ソフトウェアはオープンソースによるフリーウェアです\n"
     "本ソフトウェアを使用した結果、いかなる損害を被ったとしても\n"
     "本ソフトウェアの制作者は一切の責任を追うことはありません\n"
     "本ソフトウェアのライセンスはGPLv2です\n");

}




/* (eof) */