MSC

「MSC」の編集履歴(バックアップ)一覧に戻る

MSC - (2017/12/28 (木) 22:39:20) のソース

#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()