スターオーシャン1 レビュー

オリジナル版は非力なSFCで演出を頑張った。
ゲーム性はそれほどでもないと思う。
SO2がなかったら単体ではリメイクされなかったと思う。

バトルは単調に感じられる。
難易度低めだから、攻撃ボタンを連打するだけで何とかなることが多い。
もう少し難しくても良かった。
クリア後のエクストラダンジョンは、逆に難易度が高すぎる。
最後までプレイする気にならなかった。

ワールドマップは狭い。1本道で面白味がない。
(当時はROMの容量が少なかったから仕方ないのだろう)

狭いマップで、間を持たせようとしているせいか、
移動速度が遅かったり、クエストで同じ場所を往復させられたりする。

一応、R2+Lスティックで高速移動は出来るが
それやるとゲームバランスが崩れるような気もする。

あと、ストーリー展開が速すぎるところがあったり、
人探しのクエストでやたら時間がかかったりする。

グランディア HDコレクション レビュー

斜め見下ろし型で視界が狭い。窮屈。
どこ歩いているんだか分からない。
フィールドマップやミニマップがない。

1作目のストーリーは表面的でご都合主義。
感動の押し売り。
作風はラピュタやナディア、古いアニメな感じ。
キャラは深掘りされない。
ミューレンは一体何がしたいんだか。

バトルはそこそこ面白かった。
スキルや魔法それぞれに経験値がある。
使えば使うほどそれぞれの技能が育つ。
テーマ曲は良曲。いろんな場面で使い回される。

パーティメンバーが途中で離脱する。
ジャスティン、フィーナ以外、育て甲斐がない。

フィーナの声優、日高のり子の声がいい。

2作目のストーリーは1より断然良い。
視界の窮屈な感じも多少和らいでいる。

奇妙な遺跡からの脱出(Switch)レビュー

謎解きは面白いが、操作性悪い、ボリューム少ない

【操作性が悪い】
マウス操作用のゲーム。
操作システムがパッドに合わせて作られていない。
何をするにも、いちいちポインター(マウスカーソル)を移動させないといけない。
メニュー選択も、選択項目にポインターを合わせなければいけない。
メッセージを閉じるのも、メッセージ文の末尾の▼にポインターを移動させて、Aボタンを押さないといけない。
(Switch携帯モードの画面タッチで操作するなら、そんなにストレスは感じないのか?)

操作性については以下のゲームも同様。

からくりホテルからの脱出
からくり刑務所からの脱出
アトリエ喫茶ふらっとからの脱出
ストレンジパークからの脱出
ストレンジミュージアムからの脱出
トイハウスからの脱出
レトロハウスからの脱出
光と鏡の間からの脱出
奇妙な遺跡からの脱出
思い出の母校からの脱出
猫様のお宿からの脱出
猫様の宇宙船からの脱出
猫様の山小屋からの脱出
猫様の車窓からの脱出
病棟からの脱出
要塞刑務所からの脱出
連邦刑務所からの脱出
開かずの庭からの脱出


【ボリューム少ない】
3時間くらいでクリアできる。部屋数も30部屋もない。

連番の付いた複数の画像ファイルを非同期でダウンロードするJScript(WSH)スクリプト

URL、フォルダ名を書き換えれば好きに使える。
連番がゼロ埋めの固定桁の場合は createUrl(i) をカスタマイズする。

連番を1から始め、HTTPステータス404 Not Foundが返ってくるまでファイルをダウンロードする。
なお、本スクリプトを実行すると、Internet Explorerに履歴が残る。

AsyncDownload.js

//フォルダ名、URLを書き換えて実行
var folderName = 'MyDownloadFolder';
var baseUrl = 'https://aaaa/bbbb/cccc/6.jpg';

//URLのファイル名の数字部を '#' に変換。
baseUrl = baseUrl.replace(/([^\/]*)\d+([^\/]*)\.(jpg|png)$/i, '$1#$2.$3');
//例)http://aaa/xxx/123.jpg --> http://aaa/xxx/#.jpg

//連番からURLを作成する関数 (カスタマイズ用)
function createUrl(i) {
/*
  簡単な処理の例
    return 'http://xxxx/xxxx/' + i + '.jpg'
    return baseUrl + i + '.jpg'

  ゼロ埋めする例 (URLが http://xxxx/xxxx/b0041.jpg のような場合)
    var seq = ('0000' + i).slice(-4);  //例: 23 を '0023' に編集
    return 'http://xxxx/xxxx/b' + seq + '.jpg'
*/
  return baseUrl.replace('#', i);
}

//連番の最大値。ただし HTTP Status 404 Not Found が返ってきた場合、
//この値を更新し後続のHTTP要求を停止させている
var endIndex=999;


//詳細ログを出力する場合、@debug_log に true を設定する
/*@cc_on @*/
/*@set @debug_log = false @*/

var maxThreads = 35;
var timeoutMsec = 90 * 1000;
var waitTimeMsec = 100;
var requestedMaxIndex = 0;  //requestDownload 関数を呼び出した最大のインデックスを格納
var arrayLength = endIndex + 1; //配列はインデックス 1 の要素から使用する
var active = fillValues(new Array(arrayLength), false);
var httpStatus = fillValues(new Array(arrayLength), 0);
var xhr = new Array(arrayLength);
var info = new Array(arrayLength);
var fso = new ActiveXObject('Scripting.FileSystemObject');
var logFilePath = WScript.ScriptFullName.replace(/\.[^.]+$/, '.log');
var errLogFilePath = WScript.ScriptFullName.replace(/\.[^.]+$/, '.Error.log');
var prcsStartTime = void 0;

function main() {

  deleteFile(logFilePath);

  deleteFile(errLogFilePath);

  /*@if(@debug_log)
  putLog('START');
  @end @*/

  //ダウロード用のフォルダを作成(カレントフォルダ内)
  var folderPath = createFolder(downloadFolderName);

  prcsStartTime = new Date();

  //HTTP要求の発行
  requestAll(folderPath);

  //HTTPステータス404が返される前、先んじて発行されたHTTP要求を取り消す
  cancelVainRequests();
  function cancelVainRequests() {
    var i;
    for(i = endIndex + 1; i <= requestedMaxIndex; i++) {
      if(active[i]) {
        abortWork(i);
      }
    }
  }

  //すべてのスレッドが完了するまで待つ
  waitForCompletion();

  var i;
  for(i = 0; i < 3; i++) {
    retryDownload();

    //すべてのスレッドが完了するまで待つ
    waitForCompletion();
  }

  reportSummary();
}

function requestAll(folderPath) {
  //URLからファイル名を抜き出すための正規表現オブジェクト
  var reFileName = /[^\/]+$/;

  var i;
  for(i = 1; i <= endIndex; i++) {
    var url = createUrl(i);
    var fileName = url.match(reFileName)[0];
    var filePath = fso.BuildPath(folderPath, fileName);

    endIndex = getNewEndIndex();

    while(countActiveThread() >= maxThreads) {
      waitAnyone();
      endIndex = getNewEndIndex();
      if(i > endIndex) {
        break;
      }
    }

    if(i > endIndex) {
      break;
    }

    info[i] = new ProcessInfo(url, filePath);
    requestDownload(url, filePath, i);
  }
  /*@if(@debug_log)
  putLog('**** All Url requested. ****');
  @end @*/
}

function retryDownload() {
  var i;
  for(i = 1; i <= endIndex; i++) {
    if(info[i] != null) {
      if(!fso.FileExists(info[i].filePath)) {
        requestDownload(info[i].url, info[i].filePath, i);
      }
    }
  }
}

function requestDownload(url, filePath, index) {

  if(index > requestedMaxIndex) {
    requestedMaxIndex = index;
    //この関数を呼び出した最大のインデックスを格納
  }

  //HTTP通信を行うためのオブジェクト
  var http = getHttpObject();
  xhr[index] = http;
  active[index] = true;
  info[index].startTime = new Date();

  //コールバックを登録する
  http.onreadystatechange = function () {
    if (http.readyState == 4) {
      try {
        httpStatus[index] = http.status;
        if (http.status == 200) {
          var adTypeBinary = 1, adSaveCreateOverWrite = 2;

          //バイナリファイルを保存するためのオブジェクト
          var st = new ActiveXObject('Adodb.Stream');
          //保存する
          st.Type = adTypeBinary;
          st.Open();
          st.Write(http.responseBody); //書き込み
          st.Savetofile(filePath, adSaveCreateOverWrite); //保存
          st.Close();
        }
      } finally {
        active[index] = false;
        /*@if(@debug_log)
        endTime[index] = new Date();
        varsInfoAtEnd[index] = varsToString();
        @end @*/
      }
    }
    //サブスレッドで動くのはこのコールバックのみ
    //
    //注意
    //サブスレッドでは競合の恐れがあるグローバル変数やグローバルオブジェクトを変更するような関数、
    //putLogやputLog2などのログ関数などをコールしないこと
  };

  var t = timeoutMsec;
  http.setTimeouts(t, t, t, t);

  try {
    http.open('GET', url, true);
    //http.setRequestHeader('Content-type', 'image/jpeg');
    //http.setRequestHeader('Content-type', 'image/png');

    /*@if(@debug_log)
    putLog3(index, 'send', url);
    @end @*/
    http.send(null); //送信
  } catch(e){
    handleError(e, index)
  }
}

function getHttpObject() {
  var i;
  for(i = 1; i <= requestedMaxIndex; i++) {
    if(!active[i] && xhr[i] != null) {
      var http = xhr[i];
      xhr[i] = null;
      try {
        xhr[i].abort();
      } catch(e){}
      return http;
    }
  }
  return new ActiveXObject('Msxml2.ServerXMLHTTP');
}

function deleteFile(filePath) {
  try {
    if(fso.FileExists(filePath)) {
      fso.DeleteFile(filePath, true);
    }
  } catch(e) {
    WScript.Echo(createErrorMessage(e) + '\n\n' + quote(filePath) + ' を削除できません');
    WScript.Quit(getErrorCode(e));
  }
}

function createFolder(folderName) {

  var basePath = fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), folderName);
  var folderPath = basePath;

  var i = 1;
  while(fso.FolderExists(folderPath) || fso.FileExists(folderPath)) {
    i++;
    folderPath = basePath + ' (' + i + ')';
  }

  fso.CreateFolder(folderPath);

  return folderPath;
}

function toHex(n) {
  return (n >>> 0).toString(16).toUpperCase()
}

function fillValues(array, value) {
  var i;
  for(i = 0; i < array.length; i++) {
    array[i] = value;
  }
  return array;
}

function ProcessInfo(url, filePath) {
  this.url = url;
  this.filePath = filePath;
}

function getNewEndIndex() {
  var i;
  for(i = 1; i <= requestedMaxIndex && i < endIndex; i++) {
    if(httpStatus[i] == 404) {
      /*@if(@debug_log)
      putLog('[endIndex] will be updated by ' + i);
      @end @*/
      return i - 1;
    }
  }
  return endIndex;
}

function countActiveThread() {
  var count = 0;
  var i;
  for(i = 1; i <= requestedMaxIndex; i++) {
    if(active[i]) {
      count++;
    }
  }
  return count;
}

function waitForCompletion() {
  do {
    waitAll();
    /*@if(@debug_log)
    putLog('**** waitForCompletion Loop ****');
    @end @*/
  } while(countActiveThread() != 0)
}

function waitAnyone() {
  wait(true);
}

function waitAll() {
  wait(false);
}

function wait(waitOne) {

  var i;
  for(i = 1; i <= requestedMaxIndex; i++) {
    if(active[i] && xhr[i] != null) {
      try {
        /*@if(@debug_log)
        putLog2(i, 'waitForResponse(' + timeoutMsec + 'msec) START');
        @end @*/

        xhr[i].waitForResponse(waitTimeMsec);

        if(waitOne) {
          return;
        }
      } catch(e) {
        handleError(e, i);
      } finally {
        /*@if(@debug_log)
        putLog2(i, 'waitForResponse(' + timeoutMsec + 'msec) END  ');
        @end @*/
      }
    }
  }

}

//ErrorCode
var WININET_E_TIMEOUT             = 0x80072EE2;  //The operation timed out.
var WININET_E_INVALID_URL         = 0x80072EE5;  //The URL is invalid.
var WININET_E_UNRECOGNIZED_SCHEME = 0x80072EE6;  //The URL does not use a recognized protocol.
var WININET_E_NAME_NOT_RESOLVED   = 0x80072EE7;  //The server name or address could not be resolved.
var WININET_E_PROTOCOL_NOT_FOUND  = 0x80072EE8;  //A protocol with the required capabilities was not found.

function handleError(e, i) {

  var errMsg = createErrorMessage(e);

  switch(e.number >>> 0) {
    case WININET_E_INVALID_URL:
    case WININET_E_UNRECOGNIZED_SCHEME:
    case WININET_E_NAME_NOT_RESOLVED:
    case WININET_E_PROTOCOL_NOT_FOUND:
      WScript.Echo(quote(info[i].url) + '\n\n' + errMsg);
      WScript.Quit(getErrorCode(e));
      break;
    case WININET_E_TIMEOUT:
      /*@if(@debug_log)
      if (i > 0) {
        putLog2(i, 'Timeout');
      }
      @end @*/
      break;
  }

  try {
    putLog2(i, quote(info[i].url) + ' ' + errMsg);
  } catch(e) {}

  abortWork(i);

  /*@if(@debug_log)
  putLog3(i, 'FAILED', info[i].url);
  @end @*/
}

function createErrorMessage(e) {
  var description = e.description.replace(/\s+$/g, '');

  switch(e.number >>> 0) {
    case WININET_E_UNRECOGNIZED_SCHEME:
      description = 'The URL does not use a recognized protocol';
  }
  return 'Error: ' + description + ' (0x' + toHex(e.number) + ')';
}

function getErrorCode(e) {
  return e.number & 0xFFFF;
}

function abortWork(i) {
  try {
    if(xhr[i]) {
      try {
        xhr[i].abort();
        /*@if(@debug_log)
        abortTime[i] = new Date();
        @end @*/
      } catch (e) {}
    }
  } finally {
    active[i] = false;
    /*@if(@debug_log)
    putLog3(i, 'Abort', info[i].url);
    @end @*/
  }
}

function reportSummary() {
  var tsErrLog = void 0;

  var errorReporter = function (msg) {
    try {
      var ForWriting = 2, Unicode = -1;
      tsErrLog = tsErrLog || fso.OpenTextFile(errLogFilePath, ForWriting, true, Unicode);
      tsErrLog.WriteLine(msg);
    } catch(e){}
  }

  var summary = checkResult(errorReporter);

  tsErrLog && tsErrLog.Close();

  /*@if(@debug_log)
  emptyLine();
  putLog('File Count = ' + summary.fileCount + ' / ' + summary.total + ', Error Download = ' + summary.errorCount);
  @end @*/

  var prcsEndTime = new Date();
  var elapsedTime = Math.round(((prcsEndTime - prcsStartTime) / 1000)*10)/10;

  /*@if(@debug_log)
  putLog('END (Elapsed Time:' + elapsedTime + 'sec)');
  writeLogFile(logFilePath);
  @end @*/

  WScript.Echo('File Count = ' + summary.fileCount + ' / ' + summary.total
          + (summary.errorCount? ('\n\nError Download = ' + summary.errorCount) : '')
          + '\n\nElapsed Time = ' + elapsedTime + 's' );
}

function checkResult(errLogFunc) {
  var ret = new Object();
  ret.fileCount = 0;
  ret.errorCount = 0;

  var i;
  for(i = 1; i <= requestedMaxIndex; i++) {
    if(info[i] != null) {
      if (i == 1 || i <= endIndex) {
        if(fso.FileExists(info[i].filePath)) {
          ret.fileCount++;
        } else {
          if(typeof(errLogFunc) === 'function') {
            var errMsg = 'Download Error: ' + info[i].url;
            errLogFunc && errLogFunc(errMsg);
          }
          ret.errorCount++;
        }
      }
      /*@if(@debug_log)
      if(endTime[i]) {
        if(httpStatus[i] != 200) {
          storeLog(endTime[i], false, i, logMsg2(i, 'Returned HTTP Status: ' + httpStatus[i]) );
        }
        var label = fso.FileExists(info[i].filePath) ? 'done' : 'FAILED';
        storeLog(endTime[i], false, i, logMsg2(i, logMsg4(label, info[i].url, varsInfoAtEnd[i])));
      }
      @end @*/
    }
  }
  ret.total = ret.fileCount + ret.errorCount;

  return ret;
}

/*@if(@debug_log)
var endTime = new Array(arrayLength);
var abortTime = new Array(arrayLength);
var varsInfoAtEnd = new Array(arrayLength);
var logBuf = new Array();

function LogLine(timestamp, fromMainThread, index, logSeqno, message) {
  this.timestamp = timestamp;
  this.message = message;

  this.sortKey1 = timestamp.getTime();
  this.sortKey2 = fromMainThread? logSeqno : 0;
  this.sortKey3 = index;
  this.sortKey4 = logSeqno;

  //ログ出力順
  //まず、タイムスタンプで並び替える
  //タイムスタンプが同じなら
  //サブスレッドのログを先、メインスレッドのログは後
  //さらにメインスレッドのログは、ログ登録順
  //サブスレッドは、インデックス、ログ登録順
}

function storeLog(timestamp, fromMainThread, index, message) {
  var logSeqno = logBuf.length + 1;
  var logLine = new LogLine(timestamp, fromMainThread, index, logSeqno, message);
  logBuf.push(logLine);
}

function writeLogFile(logFilePath) {

  sortLogLines();

  var ForWriting = 2, Unicode = -1;
  var ts = fso.OpenTextFile(logFilePath, ForWriting, true, Unicode);

  var i;
  for(i = 0; i < logBuf.length; i++) {
    var s = logBuf[i].message;
    if(s == null) {
      ts.WriteLine();
    } else {
      s = dateToString(logBuf[i].timestamp) + '  ' + s;
      ts.WriteLine(s);
    }
  }
  ts.Close();
}

function sortLogLines() {

  function compareFunc(line1, line2) {
    return sign(line1.sortKey1 - line2.sortKey1 ||
          line1.sortKey2 - line2.sortKey2 ||
          line1.sortKey3 - line2.sortKey3 ||
          line1.sortKey4 - line2.sortKey4);
    //参考
    //JScript.NET(WSH JScriptではない)では比較関数が -1, 0, 1 の
    //いずれかの固定値を返さないと 0x80000000以上の値が正しくソートされない
  }

  logBuf.sort(compareFunc);
}

function sign(x) {
  return x > 0 ? 1 : x < 0 ? -1 : 0;
}
@end @*/

function putLog(str) {
  putLog2(0, str);
}

function putLog2(index, str) {
  /*@if(@debug_log)
    var fromMainThread = true;
    storeLog(new Date(), fromMainThread, index, logMsg2(index, str));
  @else
    if (str != null){
      str = dateToString(new Date()) + '  ' + logMsg2(index, str);
    }
    try {
      var ForAppending = 8, Unicode = -1;
      var ts = fso.OpenTextFile(logFilePath, ForAppending, true, Unicode);
      if(str == null) {
        ts.WriteLine();
      } else {
        ts.WriteLine(str);
      }
      ts.Close();
    } catch(e){}
  @end @*/
}

function putLog3(index, label, url) {
  putLog2(index, logMsg3(label, url));
}

function logMsg2(index, str) {
  if (str && index > 0) {
    return '(' + index + ') ' + str;
  } else {
    return str;
  }
}

function logMsg3(label, url) {
  return logMsg4(label, url, varsToString());
}

function logMsg4(label, url, varsInfo) {
  return (label + '        ').slice(0,  8) + quote(url) + ' ' + varsInfo;
}

function varsToString() {
  return '[Thread Count=' + padStart(countActiveThread(), ' ', 3) +
      ', endIndex=' + padStart(endIndex, ' ', 4) +
      ', requestedMaxIndex=' + padStart(requestedMaxIndex, ' ', 4) +
      ']';
}

function emptyLine() {
  putLog(void 0);
}

function quote(s) {
  return (s == null) ? '' : '\'' + s + '\'';
}

function padStart(value, padString, targetLength) {
  var str = String(value);
  return ('xxxxxxxxxx'.replace(/x/g, padString) + str).slice(-targetLength);
}

function padZero(value, targetLength) {
  return padStart(value, '0', targetLength);
}

function dateToString(dt) {
  var s = '';
  s += dt.getFullYear();
  s += '/' + padZero(dt.getMonth() + 1, 2);
  s += '/' + padZero(dt.getDate(), 2);
  s += ' ' + padZero(dt.getHours(), 2);
  s += ':' + padZero(dt.getMinutes(), 2);
  s += ':' + padZero(dt.getSeconds(), 2);
  s += '.' + padZero(dt.getMilliseconds(), 3);
  return s;
}

main();

連番の付いた複数の画像ファイルをダウンロードするJScript(WSH)スクリプト ― 同期処理

URL、フォルダ名を書き換えれば好きに使える。
連番がゼロ埋めの固定桁の場合は createUrl(i) をカスタマイズする。

連番を1から始め、HTTPステータス404 Not Foundが返ってくるまでファイルをダウンロードする。
なお、本スクリプトを実行すると、Internet Explorerに履歴が残る

非同期でダウンロードするスクリプトも作った。400ファイルが15秒ほど速くダウンロード出来る。
連番の付いた複数の画像ファイルを非同期でダウンロードするJScript(WSH)スクリプト - happynow’s diary


SyncDownload.js

var folderName = 'DownloadFolder';
var baseUrl = 'http://aaa/xxx/123.jpg';

// URLのファイル名の数字部を # に変換。例)http://aaa/xxx/123.jpg を http://aaa/xxx/#.jpg に変換
baseUrl = baseUrl.replace(/\d+\.(jpg|png)$/i, '#.$1');

//連番からURLを作成する関数 (カスタマイズ用)
function createUrl(i) {

  return baseUrl.replace('#', i);

/*
  簡単な処理の例
    return 'http://xxxx/xxxx/' + i + '.jpg'
    return baseUrl + i + '.jpg'

  ゼロ埋めする例 (URLが http://xxxx/xxxx/b0041.jpg のような場合)

    var seq = ('0000' + i).slice(-4);  //例: 23 を '0023' に編集
    return 'http://xxxx/xxxx/b' + seq + '.jpg'
*/
}


//連番の最大値。ただし HTTP Status 404 Not Found が返ってきた場合、この値を更新し後続のHTTP要求を停止させている
var maxIndex=999;

var fso = new ActiveXObject('Scripting.FileSystemObject');

var logFilePath = WScript.ScriptFullName.replace(/\.[^.]+$/, '.log');

try {
  if(fso.FileExists(logFilePath)) {
    //既存のログファイルを削除する
    fso.DeleteFile(logFilePath, true);
  }
} catch(e) {}

var startTime = new Date();
putLog('START');

var fileCount = 0;

//ダウロード用のフォルダ作成(カレントフォルダ配下)
var folderPath = createDownloadFolder(folderName);

//HTTP要求オブジェクトの作成
var xhr = new ActiveXObject('Msxml2.ServerXMLHTTP');

//URLからファイル名を抜き出すための正規表現オブジェクト
var reFileName = /[^\/]+$/;

var i;
for(i = 1; i <= maxIndex; i++) {

  var url = createUrl(i);
  var fileName = url.match(reFileName)[0];
  var filePath = fso.BuildPath(folderPath, fileName);

  //putLog(i + ' url=' + url);
  //putLog(i + ' fileName=' + fileName);

  downloadFile(url, filePath, i);
}

emptyLine();
putLog('File Count=' + fileCount);

var endTime = new Date();
var elapsedTime = Math.round(((endTime - startTime) / 1000)*10)/10;
putLog('END (Elapsed Time:' + elapsedTime + 'sec)');

WScript.Echo('File Count=' + fileCount);


function downloadFile(url, filePath, index) {

  try {
    emptyLine();
    var log_i = function (s) { putLog('[' + index + '] ' + s); };

    var t = 90 * 1000;
    xhr.setTimeouts(t,t,t,t);

    xhr.open('GET', url, false);
    xhr.setRequestHeader('Content-type', 'image/jpeg');

    log_i('send ' + url);
    xhr.send(null); // 送信

    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        var adTypeBinary = 1, adSaveCreateOverWrite = 2;

        // バイナリファイルを保存するためのオブジェクト
        var strm = new ActiveXObject('Adodb.Stream');

        strm.Type = adTypeBinary;
        strm.Open();
        strm.Write(xhr.responseBody); // 書き込み
        strm.Savetofile(filePath, adSaveCreateOverWrite); // 保存
        strm.Close();

      } else {
        log_i('retured HTTP status : ' + xhr.status);
        if (xhr.status == 404) {
          if (index < maxIndex) {
            maxIndex = index;
          }
        }
      }
    }
  } catch(e){
    var WININET_E_TIMEOUT             = 0x80072EE2;  //The operation timed out.
    var WININET_E_INVALID_URL         = 0x80072EE5;  //The URL is invalid.
    var WININET_E_UNRECOGNIZED_SCHEME = 0x80072EE6;  //The URL does not use a recognized protocol.
    var WININET_E_NAME_NOT_RESOLVED   = 0x80072EE7;  //The server name or address could not be resolved.
    var WININET_E_PROTOCOL_NOT_FOUND  = 0x80072EE8;  //A protocol with the required capabilities was not found.

    function createErrorMessage(e) {
      var description = e.description.replace(/\s+$/g, '');

      switch(e.number >>> 0) {
        case WININET_E_UNRECOGNIZED_SCHEME:
          description = 'The URL does not use a recognized protocol';
      }
      return 'Error: ' + description + ' (0x' + toHex(e.number) + ')';
    }

    var errorCode = e.number & 0xFFFF;

    switch(e.number >>> 0) {
      case WININET_E_INVALID_URL:
      case WININET_E_UNRECOGNIZED_SCHEME:
      case WININET_E_NAME_NOT_RESOLVED:
      case WININET_E_PROTOCOL_NOT_FOUND:

        WScript.Echo(url + '\n\n' + createErrorMessage(e) );
        WScript.Quit(errorCode);
        break;
      case WININET_E_TIMEOUT:
        log_i('timeout');
        break;
    }

    log_i(createErrorMessage(e));

  } finally {
    if(fso.FileExists(filePath)) {
      log_i('done ' + url);
      fileCount++;
    } else {
      log_i('failed ' + url);
    }
    xhr.abort();
  }
}

function createDownloadFolder(folderName) {

  var basePath = fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), folderName);
  var folderPath = basePath;

  var i = 1;
  while(fso.FolderExists(folderPath) || fso.FileExists(folderPath)) {
    i++;
    folderPath = basePath + ' (' + i + ')';
  }

  fso.CreateFolder(folderPath);

  return folderPath;
}

function toHex(n) {
  return (n >>> 0).toString(16).toUpperCase()
}

function putLog(s) {
  if (s != null){
    s = dateToString(new Date()) + '  ' + s;
  }
  try {
    var ForAppending = 8, Unicode = -1;
    var ts = fso.OpenTextFile(logFilePath, ForAppending, true, Unicode);
    if(s == null) {
      ts.WriteLine();
    } else {
      ts.WriteLine(s);
    }
    ts.Close();
  } catch(e) {}
}

function emptyLine() {
  putLog(void 0);
}

function dateToString(dt) {
  var s = '';
  s += dt.getFullYear();
  s += '/' + ('00' + (dt.getMonth() + 1)).slice(-2);
  s += '/' + ('00' + dt.getDate()).slice(-2);
  s += ' ' + ('00' + dt.getHours()).slice(-2);
  s += ':' + ('00' + dt.getMinutes()).slice(-2);
  s += ':' + ('00' + dt.getSeconds()).slice(-2);
  s += '.' + ('000' + dt.getMilliseconds()).slice(-3);
  return s;
}

JScript.NETコンパイラ(jsc.exe)で「error JS1135: 変数 'WScript' が宣言されていません」のエラーが出たので WScript を自作した

まず、この自作したWScriptを使った、WSH JScriptファイルのコンパイル方法を説明する。
下記の wscr.js (自作版WScript) と sample.js (コンパイル対象のWSHスクリプト) を同一フォルダに格納する。
ソースファイルを保存する際、エンコードは「Unicode(UTF16) BOMなし」「UTF7 BOMなし」を避けること。
格納後、次のコマンドを実行。

"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\jsc.exe" /out:sample.exe wscr.js sample.js

wscr.js (自作版WScript)

//wscr.js
import System;
import System.IO;
import System.Threading;
import System.Diagnostics;
import System.Windows.Forms;
import Microsoft.JScript;

function MsgBox(value) {
  MessageBox.Show(value, 'Windows Script Host', MessageBoxButtons.OK, MessageBoxIcon.None);
}

class WScriptBaseClass {
  protected const _args = Environment.GetCommandLineArgs();
  protected const _argsObj = new WshArguments();
  protected var _getObj = GetObject;

  public class WshArguments {
    public function Item(i : int) {
      return _args[i + 1];
    }
    public function Count() {
      return _args.length - 1;
    }
    public function get length() {
      return _args.length - 1;
    }
  }

  public function Arguments(i : int) {
    return _args[i];
  }
}

class WScriptClass extends WScriptBaseClass {

  public function get Arguments() { return _argsObj; }

  //コマンドラインから起動したかどうかをメインウィンドウの有無で判定
  private const runFromCommandLine = (Process.GetCurrentProcess().MainWindowHandle === IntPtr.Zero);

  private const _wshExeName = runFromCommandLine ? 'cscript.exe' : 'wscript.exe';

  public const Path = Environment.GetFolderPath(Environment.SpecialFolder.System);
  public const FullName = System.IO.Path.Combine(Path, _wshExeName);
  public const Name = 'Windows Script Host';
  public const ScriptFullName = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName);
  public const ScriptName = AppDomain.CurrentDomain.FriendlyName;
  public const Version = '5.812';

  public function Echo(value) {
    if(runFromCommandLine) {
      print(value);
    } else {
      MsgBox(value);
    }
  }

  public function Quit(errorCode){
    Environment.Exit(errorCode);
  }

  public function CreateObject(strProgID : String) {
    return new ActiveXObject(strProgID);
  }

  public function GetObject(strPathname : String, strProgID : String, strPrefix : String) {
    if (strPathname === '' && strProgID !== '') {
      return new ActiveXObject(strProgID);
    } else {
      if (strProgID === void 0) {
        return _getObj(strPathname);
      } else {
        return _getObj(strPathname, strProgID);
      }
    }
  }

  public function Sleep(intTime : int) {
    System.Threading.Thread.Sleep(intTime);
  }

  private var _fso;
  private function get fso() {
    _fso = _fso || new ActiveXObject('Scripting.FileSystemObject');
    return _fso;
  }

  private var _stdIn;
  public function get StdIn() {
    _stdIn = _stdIn || fso.GetStandardStream(0);
    return _stdIn;
  }

  private var _stdOut;
  public function get StdOut() {
    _stdOut = _stdOut || fso.GetStandardStream(1);
    return _stdOut;
  }

  private var _stdErr;
  public function get StdErr() {
    _stdErr = _stdErr || fso.GetStandardStream(2);
    return _stdErr;
  }
}

var WScript = new WScriptClass();

sample.js (コンパイル対象のWSHスクリプト)

// sample.js
// (WSHでの実行方法)コマンドラインで以下を実行
// cscript sample.js aaaa bbbb cccc

var puts = function (s) {
  WScript.StdOut.WriteLine(s);
}

puts('typeof WScript.Arguments='+(typeof WScript.Arguments));
puts('WScript.Arguments.length = ' + WScript.Arguments.length);
var i;
for(i = 0; i < WScript.Arguments.length; i++) {
  puts('WScript.Arguments.Item(' + i + ') = ' + WScript.Arguments.Item(i));
  puts('WScript.Arguments(' + i + ') = ' + WScript.Arguments(i));
}

puts('WScript.FullName = ' + WScript.FullName);
puts('WScript.Name = ' + WScript.Name);
puts('WScript.Path = ' + WScript.Path);
puts('WScript.ScriptFullName = ' + WScript.ScriptFullName);
puts('WScript.ScriptName = ' + WScript.ScriptName);
puts('WScript.Version = ' + WScript.Version);

var msec = 1 * 1000;
puts('WScript.Sleep(' + msec + ') START : ' + dateToString(new Date()) );
WScript.Sleep(msec);
puts('WScript.Sleep(' + msec + ') END   : ' + dateToString(new Date()) );

WScript.StdOut.WriteLine('WScript.StdOut.WriteLine');
WScript.StdErr.WriteLine('WScript.StdErr.WriteLine');
/*
WScript.StdErr.Write('Enter Text>');
var text = WScript.StdIn.ReadLine();
WScript.StdErr.WriteLine('Entered : "' + text + '"');
*/

WScript.Echo('WScript.Echo');

var errorCode = 3;
puts('WScript.Quit(' + errorCode + ')');
puts('after this process done, enter \'echo \%errorlevel\%\'');
WScript.Quit(errorCode);

function dateToString(dt) {
  var s = '';
  s += dt.getFullYear();
  s += '/' + ('00' + (dt.getMonth() + 1)).slice(-2);
  s += '/' + ('00' + dt.getDate()).slice(-2);
  s += ' ' + ('00' + dt.getHours()).slice(-2);
  s += ':' + ('00' + dt.getMinutes()).slice(-2);
  s += ':' + ('00' + dt.getSeconds()).slice(-2);
  s += '.' + ('000' + dt.getMilliseconds()).slice(-3);
  return s;
}


上記の自作版WScriptには下記の制限がある。

  • 自作版WScript のメソッド名、プロパティ名は Case-Sensitive である。大文字小文字を wscr.js で定義されている通りに書かなければならない。
  • 自作版WScript の GetObject メソッドには、引数 strPathname と strProgID を同時に指定しない。3番目の引数 strPrefix は指定できない。


さて、実装では以下の点が問題となった。

(1) デフォルトメソッドを呼び出すとき、オブジェクトに対して関数呼び出しの書き方ができる。

  • 関数呼び出しの記述
    WScript.Arguments(2)
  • 通常のオブジェクト参照
    WScript.Arguments.length
    WScript.Arguments.Item(2)

これらの書き方を両立させるのが難しい

(2) JScriptにおいてもWScriptのメソッド名はCase-Insensitiveである。

(3) WScript.GetObject と JScript.NET の組み込み関数 GetObject の動作が異なる。
WScript.GetObject(strPathname [,strProgID], [strPrefix]) の引数 strPrefix が、JScript.NET の GetObject にはない。
strPathname,strProgID を同時に与えたときの動作が異なる。


(2), (3) については対処できなかった。

ちなみに条件コンパイルを使えば、WScriptの実装とWSH用のスクリプトは下記のように同一ファイルにまとめることができる。
このソースはそのままで、WSHスクリプトとして動かせるし、JScript.NETファイルとしてコンパイルできる。

//sample2.js
/*@cc_on
@if (@_jscript_version >= 7)
import System;
import System.IO;
import System.Threading;
import System.Diagnostics;
import System.Windows.Forms;
import Microsoft.JScript;

function MsgBox(value) {
  MessageBox.Show(value, 'Windows Script Host', MessageBoxButtons.OK, MessageBoxIcon.None);
}

class WScriptBaseClass {
  protected const _args = Environment.GetCommandLineArgs();
  protected const _argsObj = new WshArguments();
  protected var _getObj = GetObject;

  public class WshArguments {
    public function Item(i : int) {
      return _args[i + 1];
    }
    public function Count() {
      return _args.length - 1;
    }
    public function get length() {
      return _args.length - 1;
    }
  }

  public function Arguments(i : int) {
    return _args[i];
  }
}

class WScriptClass extends WScriptBaseClass {

  public function get Arguments() { return _argsObj; }

  //コマンドラインから起動したかどうかをメインウィンドウの有無で判定
  private const runFromCommandLine = (Process.GetCurrentProcess().MainWindowHandle === IntPtr.Zero);

  private const _wshExeName = runFromCommandLine ? 'cscript.exe' : 'wscript.exe';

  public const Path = Environment.GetFolderPath(Environment.SpecialFolder.System);
  public const FullName = System.IO.Path.Combine(Path, _wshExeName);
  public const Name = 'Windows Script Host';
  public const ScriptFullName = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName);
  public const ScriptName = AppDomain.CurrentDomain.FriendlyName;
  public const Version = '5.812';

  public function Echo(value) {
    if(runFromCommandLine) {
      print(value);
    } else {
      MsgBox(value);
    }
  }

  public function Quit(errorCode){
    Environment.Exit(errorCode);
  }

  public function CreateObject(strProgID : String) {
    return new ActiveXObject(strProgID);
  }

  public function GetObject(strPathname : String, strProgID : String, strPrefix : String) {
    if (strPathname === '' && strProgID !== '') {
      return new ActiveXObject(strProgID);
    } else {
      if (strProgID === void 0) {
        return _getObj(strPathname);
      } else {
        return _getObj(strPathname, strProgID);
      }
    }
  }

  public function Sleep(intTime : int) {
    System.Threading.Thread.Sleep(intTime);
  }

  private var _fso;
  private function get fso() {
    _fso = _fso || new ActiveXObject('Scripting.FileSystemObject');
    return _fso;
  }

  private var _stdIn;
  public function get StdIn() {
    _stdIn = _stdIn || fso.GetStandardStream(0);
    return _stdIn;
  }

  private var _stdOut;
  public function get StdOut() {
    _stdOut = _stdOut || fso.GetStandardStream(1);
    return _stdOut;
  }

  private var _stdErr;
  public function get StdErr() {
    _stdErr = _stdErr || fso.GetStandardStream(2);
    return _stdErr;
  }
}

var WScript = new WScriptClass();
@end @*/

//-------------------------------
//ここから下にWSHスクリプトを書く
var puts = function (s) {
  WScript.StdOut.WriteLine(s);
}

puts('typeof WScript.Arguments='+(typeof WScript.Arguments));
puts('WScript.Arguments.length = ' + WScript.Arguments.length);
var i;
for(i = 0; i < WScript.Arguments.length; i++) {
  puts('WScript.Arguments.Item(' + i + ') = ' + WScript.Arguments.Item(i));
  puts('WScript.Arguments(' + i + ') = ' + WScript.Arguments(i));
}

puts('WScript.FullName = ' + WScript.FullName);
puts('WScript.Name = ' + WScript.Name);
puts('WScript.Path = ' + WScript.Path);
puts('WScript.ScriptFullName = ' + WScript.ScriptFullName);
puts('WScript.ScriptName = ' + WScript.ScriptName);
puts('WScript.Version = ' + WScript.Version);

var msec = 1 * 1000;
puts('WScript.Sleep(' + msec + ') START : ' + dateToString(new Date()) );
WScript.Sleep(msec);
puts('WScript.Sleep(' + msec + ') END   : ' + dateToString(new Date()) );

WScript.StdOut.WriteLine('WScript.StdOut.WriteLine');
WScript.StdErr.WriteLine('WScript.StdErr.WriteLine');
/*
WScript.StdErr.Write('Enter Text>');
var text = WScript.StdIn.ReadLine();
WScript.StdErr.WriteLine('Entered : "' + text + '"');
*/

WScript.Echo('WScript.Echo');

var errorCode = 3;
puts('WScript.Quit(' + errorCode + ')');
puts('after this process done, enter \'echo \%errorlevel\%\'');
WScript.Quit(errorCode);

function dateToString(dt) {
  var s = '';
  s += dt.getFullYear();
  s += '/' + ('00' + (dt.getMonth() + 1)).slice(-2);
  s += '/' + ('00' + dt.getDate()).slice(-2);
  s += ' ' + ('00' + dt.getHours()).slice(-2);
  s += ':' + ('00' + dt.getMinutes()).slice(-2);
  s += ':' + ('00' + dt.getSeconds()).slice(-2);
  s += '.' + ('000' + dt.getMilliseconds()).slice(-3);
  return s;
}