#contents(,fromhere=true,level=3) ---- **MSCとは MSCとは、スマブラforにおいてキャラクターの動作を制御するプログラムのことである。ゲームシステム解析のほか、トレーニングModパックにも利用されている。各キャラクターごとに個別のMSCが存在し、ファイルは fighter>[キャラ名]>script>msc に格納されている。拡張子は「.mscsb」。 スマブラforからの新しいシステムで、ユーザーの間で解明されはじめたのは2017年秋頃からである。スクリプト数は1キャラにつき2000超。スクリプト文法や動作との対応など未解明の部分が多い。現状ではACMDファイル(≒Hitboxスクリプト)よりも敷居が高いと言える。 ---- **導入 MSCファイルを開くのに必要な環境は以下の通り。 -&bold(){.mscsbファイル} --Sm4shExplorerでExtractして入手。[[真・解析の手引き]]も参考に。 -[[&bold(){pymsc}>>https://github.com/jam1garner/pymsc]] (クリックでDLページへ) --mscsbファイルの解凍・コンパイルを行うプログラム。簡単なMSCコードを実行できるエミュレータプログラムも同梱。 --本記事で扱うプログラムファイルは以下の3つ。 ---asm.py:mscsbファイルへのコンパイル。 ---disasm.py:mscsbファイルの解凍。 ---emu.py:MSCエミュレーター。 -&bold(){Python3 のインストール} --プログラミング言語。pymscを実行するのに必要。 --Pythonの知識はほぼ必要ない(あるに越したことはない)が、コマンドプロンプトは最低限扱える必要がある。 編集に役立つサイトを以下に紹介する。 ****[[MSC Note Sheet>>https://docs.google.com/spreadsheets/d/1-Fv8UyW2eTCjDfRnN3ObRD9e6ig4zmlhU_IzCDWh86Q/edit#gid=1328226474]] 現在判明しているMSCのコマンドや内容が表にまとまっている。 ****[[pymsc wiki>>https://github.com/jam1garner/pymsc/wiki]] pymsc製作者によるMSCの文法解説。本記事でも要約を掲載する。 ****[[PyMSC Tutorial>>https://youtu.be/ZsVhBAKKha4]] トレモModパック製作者によるPyMSCツールの実データ用の使い方解説。 ****[[MSC Syntax Highlighting>>https://gamebanana.com/tools/6296]] MSCファイル編集に便利なエディタ設定ファイル。 フリーの「Atomエディタ」に導入すると、MSCの文法に反応して色を付けて表示してくれる。 [[https://gamebanana.com/tools/6296]] ---- **mscsbの解凍・コンパイル シェルスクリプトでpymscプログラムを動かし、mscsbを解凍・コンパイルする。 ***解凍 python disasm.py [mscsb file] を実行する。[]内にはmscsbファイルのパスを入力。解凍されたスクリプトファイル群は、"output"フォルダの中に展開される。出力されるのは -Scripts (拡張子なし) -globals.txt -script_[n].txt ***コンパイル "Scripts"ファイル、スクリプトの.txtファイルが存在するフォルダで python asm.py を実行する。同フォルダに"test.mscsb"が出力される。出力ファイル名は、Scriptsファイルに |example.mscsb のように「|」(バー)から始まる一行を任意の位置に追加することで変更が可能(後述)。 asm.pyはmsc.pyをインポートするので、msc.pyを同フォルダに入れるか sys.path.append( msc.pyのパス ) などを追加するなどしてパスを通さないと動作しないことに注意。 ***出力ファイルについて ****Scripts .txtのスクリプト群をリスト化・管理しているファイル。 行頭に「:」(コロン)があるスクリプトが対戦開始前に最初に読み込まれ、キャラクターの基本的な設定が行われる。 asm.pyでコンパイルするファイル名を変えたい場合は、"script"に |example.mscsb のように、「|」(バー)から始まる一行を追加すればよい。 任意の位置に追加可能で、複数個追加すれば同じ内容のファイルが(別名で)保存される。 ****globals.txt 文字のエイリアス設定をするファイル。 .string [string] これで、行数が文字列のエイリアスになる。 ***同梱のバッチファイルについて 作業効率化のためのバッチファイルのサンプルが、DLフォルダ内にzip形式で同梱されている。対応する".py"ファイルと同階層に置いて実行すれば、シェルスクリプトを使わずに解凍・コンパイルなどが可能。 ※あくまでサンプルなので、disasm.bat など引数が足りないものは自分でbatファイルを編集する必要アリ。 ---- **emu.py emu.pyは、シェルスクリプト上でMSCコードを仮想的に実行するエミュレータープログラムである。実行方法は python emu.py と打ったあとにコードを入力するか、 puthon emu.py [mscsb file] で直接実行するかのいずれか。ゲームで使われるコマンドの中にはemu.pyが対応していないものも多くあるので、後者もあくまで自作のコードを動かすための機能だと考えるべきであろう。 以下では主にemu.pyで動作するMSCの構文を学習する。 ---- **MSC構文 プログラミング言語としての構造は非常にシンプルで、基本的にスタックとスクリプトで数値をやり取りして end でプログラム終了するだけである。 ***スタック MSCの数値はスタックの形式で管理される。スタックとは、データの管理方式の一つである。変数をストックする際は上に積み上げていき、逆に使用する際は上から取っていくという形式を取る。上に積む作業を"Push"、上から取り除く作業を"Pop"と呼ぶ。 MSCでは、コマンド末尾に「.」(ピリオド)を打つことで入力をスタックにpushすることを示す。 例: pushInt. 0x30 #0x30をスタック一番上にpush スタックの概念が良く分からない人は:[[スタック(wikipedia)>>https://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF]] ***プリント ****printf [表示するstack数] stackの文字列を表示するコマンド。 表示するstack数に2以上の数を入れた場合は、上から指定数のstackを取り出し、下に積んであったものから順に表示する。数値を直接printすることはできず、文字列中に「%i」などと打つことでのみ、その位置に置き換わる形で表示できる。配置する順番等については例Bを参考に。 整数は「%i」、浮動小数は「%f」と表記する。 例A: pushInt. 1 pushInt. 2 addi. #スタックの上2つをpop&加算、結果をpush pushInt. "1 + 2 = %i" #文字列をpush。%1は、ここにintを挿入せよの意。 printf 2 #上2つを print. 例B: pushInt. 5 pushInt. 10 pushInt. "入力は順に %i, %i です." printf 3 >> 入力は順に 5, 10 です. ↑ 「5,10,文字列」の順に読み込み、文字列に含まれる「%i」に整数が入る。 ***整数と浮動小数 浮動小数を入力するときは、 pushInt. 2.5f のように、最後に「f」をつけて小数を判別する。 整数を入力するときは何もつけなくてよい。 ***エイリアス 文字列に数値または文字列を紐づけする。 .alias 3,stockCount #stockCount = 3 PushInt. 4 setVar 0,stockCount #実際は3がセットされる。 実際にはglobals.txtにグローバルエイリアス設定がある。 ***変数 MSCの変数には''global'',''local'' の2タイプがある。 前者は全スクリプト、後者は単一のスクリプト内でのみ有効。 ****begin [argument count], [variable count] 変数の個数を宣言。実際のMSCファイルでは先頭行に必ず書かれている。 エミュレーター上では書かなくてもよい。 -argument = local valのうち、既に値が代入されているものの数。 -variable = そのスクリプトで扱うlocal valの総数。 エミュレーター上でargument countを1以上に設定すると、実行時にその数の分だけ代入する数値の入力を求められる。 ****pushVar. [type], [varnum] ****setVar [type], [varnum] -type : 0=local, 1=global -varnum :変数番号。「0,5」なら、「localの5番目の変数」。 -pushVar.:変数をstackに積む。「.(ピリオド)」がpushすることを示す。 -setVar:stackから変数に代入。 例 begin 0,3 #localは3個。今、0個がストックされた状態からスタート。 pushInt. 3 #まず3をpush。 setVar 0, 0 #localの0番目に3を代入。3はpopped pushInt. 43 #43をpush。 setVar 1, 10 #globalの10番目に代入。43はpopped pushVar. 0,0 #local_0をpush pushVar. 1,10 #global_10をpush pushInt. #"localVar0 = %i globalVar10 = %i" 文字をpush printf 3 #3個をprint。上3つ(3,10,文字列)をprint end ***論理 数字(0,1)がそのまま論理に対応し、0はfalse, 1はTrueを表す。 ****論理記号 ''equals.''は、stackの上2つの数値をpopし、等しければ1を、異なれば0をpushする。 同様に、''notEquals, lessThan, lessOrEqual, greater, greaterOrEqual'' も存在する。 ****論理演算子 stackの上2つの論理(0,1)をpopし、論理判定をpushする。 ''bitOr, bitAnd, bitXor''が存在。 ***if文 ****if stackをpopし、trueの時のみif以下のコードを読む。 例: pushInt. 1 #true if endOfIf #trueなので読む pushInt. "The if was run" # printf 1 #The if was runと表示 endOfIf: #読むのはここまで pushInt. "This was run" printf 1 ****else ifで場合分けをしたい場合に使う。使用方法が少し特殊。 コマンドの効果は、「else B」で「Bまでジャンプする」というもの。ifと同時に使うことを想定されているようだが、通常のプログラムコードと違って、ifとelseの対応関係はないらしい。 例 pushInt. 0 or 1 if A pushInt. "Was true" printf 1 else B A: pushInt. "Was false" printf 1 B: pushInt. "If/Else blocks over" printf 1 end &u(){Trueのとき} Aが実行される。Aブロックの最終行「else B」で「B:」までジャンプ。 結果的に、A:とB:の間に位置するfalse時のコマンドは実行されない。 &u(){Falseのとき} Aブロックはスキップされ、A:の次の行(false時のコマンド)から読み込む。 B: はTrue時に読まない領域を区切っているだけであり、 ifコマンドそのものとは対応していない。 ***演算 計算を行うときは、演算子に整数なら「i」を、浮動小数なら「f」をつけて計算する。 演算子には以下のようなものがある(全て浮動小数版)。 |addf|加算| |subf|減算| |divf|除算| |multf|乗算| |negf|符号反転| また、local変数計算を簡潔に行う演算子もいくつかある。 |Int|Float|Parameters|Description|h |i++|f++|varType,varNum|adds 1 to variable| |i--|f--|varType,varNum|subtract 1 from variable| |i+=|float+=|varType,varNum|adds X value from stack to variable| |i-=|float-=|varType,varNum|subtracts X value from stack to variable| |i*=|float*=|varType,varNum|multiplies variable by X value from stack| |i/=|float/=|varType,varNum|divides variable by X value from stack| 例 i+= 0,1 #local_1に、stack一番上にある数値を加算する ***syscall syacallは、数値の処理を行うコマンド(の群)。 内部システムにおける様々な数値処理の実態であり、それぞれのコマンドに番号が振られている。 -処理とコマンド番号の例 --ボタン入力を取得する: 0xe --Hitbox関連の何か:0x2e --乱数を発生させる:0x9 --etc... 現在判明しているコマンドと番号の対応表↓。 [[https://docs.google.com/spreadsheets/d/1-Fv8UyW2eTCjDfRnN3ObRD9e6ig4zmlhU_IzCDWh86Q/edit#gid=1909700739]] 構文は ****sys [stackから取得する値の個数] [コマンド番号] ****sys. [stackから取得する値の個数] [コマンド番号] ←stackにpushする場合 コマンドによって必要な引数の個数が違うので、適切な数が「取得する値の個数」で指定される。 例1:コマンド 0x9 処理:2つの引数a,bを取り、a以上b未満の整数をランダムに生成する pushInt. 0 pushInt. 10 sys. 2, 0x9 #「0,10」の2つが引数。「.」があるので、作った乱数はpushされる。 pushInt. "Random number: %i" printf 2 >> Random number: 7 例2:コマンド 0x16 処理:ID(例:0x1E00000A)に割り当てられた数値にアクセスする。 引数は「処理タイプ, (数値,) 変数ID」 処理タイプが「0x6」の場合、IDに格納されている数値を呼び出す。 処理タイプが「0x7」の場合、IDに数値を書き込む。 第二引数の「数値」は、後者の場合のみ必要になる。(前者では入力不要) pushInt. 7 pushInt. 5000 pushInt. 0x2000000 sys 0x3, 0x16 #値"5000"をID"0x2000000"に書き込む。 pushInt. 6 pushInt. 0x2000000 sys. 0x2, 0x16 #ID"0x2000000"の数値をstackにpushする。 注:PyMSC Emuで動作するsyscallは、ここで紹介したコマンド0x9, 0x16のみである。 ***他のスクリプトの呼び出し ****callFunc [int] スタック最上段に置いたスクリプトを呼び出す。 intにはスクリプトの引数の個数を指定し、その個数分を追加でスタックから取得する。 例1. pushInt. script_0 callFunc 0 script_0を呼び出す。引数なし。 例2 try. label pushInt. 1 pushInt. 2 pushInt. 3 pushInt. script_200 callFunc 3 label: script_200を、引数"1,2,3"とともに渡す。 try. コマンドはこの場合、"label"に囲われたスクリプトからの出力をstackに積むという処理である。 例3 呼び出されるスクリプトの例。 begin 1,1 #1個のlocal変数があり、うち1個は既に値が代入されている pushVar. 0,0 pushInt. 2 multi. #localの0番目と2を乗算 return_6 # stackの一番上をpopしながら出力する。 end 新規に作成したファイルを呼び出すときも、そのファイル名を指定すれば問題なく呼び出せる。また"script_[n]"の形式で呼び出したファイルの名前が変更されていた場合でも、代わりに"インデックスnのスクリプト"を参照するために特に不都合は起こらない(らしい)。 ---- **コメント #comment()