したらばTOP ■掲示板に戻る■ 全部 1-100 最新50 | |

プログラム初心者がmod制作してみる 詳しい人教えて(Harmony編)

1プログラム初心者 ◆l3Flv.uj/A:2018/08/15(水) 17:38:17
欲しいのなければ自分で作れと思い最近作り始めてxmlまでは順調にできたんだが
C#になってから全くできないんだ(プログラム初心者)
Harmony(HugsLib)を使えばprivateのフィールドやメソッドにアクセスして
既存クラスを変えられるらしいが変え方が全く分からない
Harmony使っている人のソース見ても英wiki見ても理解ができない

今作ろうとしているのは襲撃ポイント変更mod
StoryWatcher_RampUpクラスに「時間経過による襲撃ポイント増加」に関わりそうな
RampUpWatcherTickメソッドを弄れば固定にしたりなくしたり
できそうなんだけどprivateだから外部から弄れないし

かれこれ、丸3日何も出来てないのでHarmony詳しい人教えてください

2プログラム初心者 ◆l3Flv.uj/A:2018/08/15(水) 17:56:32
StoryWatcher_RampUpクラスで元のソースが

private float shortTermFactor = 1f;
private float longTermFactor = 1f;

public void RampUpWatcherTick()
{
if (Find.TickManager.TicksGame % 5000 == 0)
{
if ((float)GenDate.DaysPassed >= 21f)
{
this.shortTermFactor += 0.000356125354f;
}
if ((float)GenDate.DaysPassed >= 42f)
{
this.longTermFactor += 0.000231481492f;
}
}
}

21日目以降shortTermFactorが増加していく。
42日目以降longTermFactorが増加していく。
メソッドがあったので

これを弄れば固定にしたり無効化したりできると思うんだけど
外部クラスから弄れないしHarmonyでどう変更すればいいのかも分からない
そしてどう組み込むかも

変更例 10固定
if (this.longTermFactor <= 10) {
this.longTermFactor += 0.000231481492f;
}
else { }

3プログラム初心者 ◆l3Flv.uj/A:2018/08/16(木) 14:31:00
注意:下記コードは試行錯誤中で動きません。
namespace SR_IR_fixedvalue
{
[StaticConstructorOnStartup]
class Main
{
static Main()
{
var harmony = HarmonyInstance.Create("com.test.fixedvalue");
harmony.PatchAll(Assembly.GetExecutingAssembly());
}
}

[HarmonyPatch(typeof(StoryWatcher_RampUp))]
[HarmonyPatch("RampUpWatcherTick")]

class Patch
{
static void PostFix(StoryWatcher_RampUp __instance)
{
if (Find.TickManager.TicksGame % 5000 == 0){
if (this.shortTermFactor <= 5f && (float) GenDate.DaysPassed >= 21f){
this.shortTermFactor += 0.000356125354f;
}
if (this.longTermFactor <= 5f && (float) GenDate.DaysPassed >= 42f){
this.longTermFactor += 0.000231481492f;
}
}
}
}
}

英wikiサンプルや民火様のmodのソースを参考にしていますが
未だにインスタンスを引っ張ってきてprivate変数にアクセスする方法が分かりません…

襲撃式
P =((C×42)+(iW÷100)+(BW÷200))×(SR×LR)×D×T×R
StoryWatcher_RampUpクラスの
SR → private float shortTermFactor
LR → private float longTermFactor

元のメソッドは21日目以降SRが42日目以降LRが無限に増加していくのを
SR 5以下の場合増加、5以上の場合スルー
LR 5以下の場合増加、5以上の場合スルー

が、既存クラス内ではないのでthisは使えない

PostFix内にパラメータとして__instanceをしているが
これで引っ張って来れているのだろうか…?

Harmony詳しい人…お願いします

4名無しさん:2018/08/16(木) 16:21:16
Harmony、そんなに詳しくはないですが。。。
ちなみに、Patchクラス での"this"はPatchクラスのインスタンスになりますよ。まぁ、static method なので参照できないと思いますけど。
StoryWatcher_RampUp クラスのインスタンスは __instance 引数に設定されますね。 https://github.com/pardeike/Harmony/wiki/Patching#patch-parameters
__instance の private field にアクセスしたいなら、普通にReflectionしてそのままSetValueする(A)か、DynamicMethodでGetter/SetterをILGenerateしたDelegateを呼び出す(B)かですね。

[HarmonyPatch(typeof(StoryWatcher_RampUp))]
[HarmonyPatch("RampUpWatcherTick")]
class Patch
{
// (A) Reflection して Field を取得
private static System.Reflection.FieldInfo shortTermFactorField = typeof(StoryWatcher_RampUp).GetField("shortTermFactor", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

// (B) Harmony の Generator を利用して Getter, Setter の Delegate 生成
private static Harmony.GetterHandler getShortTermFactor = Harmony.FastAccess.CreateGetterHandler(typeof(StoryWatcher_RampUp).GetField("shortTermFactor", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));
private static Harmony.SetterHandler setShortTermFactor = Harmony.FastAccess.CreateSetterHandler(typeof(StoryWatcher_RampUp).GetField("shortTermFactor", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));

static void PostFix(StoryWatcher_RampUp __instance)
{
// (A) Reflection してそのまま 値取得
var foo = field.GetValue(__instance);
// (B) ILGenerate した Getter の Delegate を呼んで値取得
var bar = (float)getShortTermFactor(__instance);

// いろいろ処理する

// (A) Reflection してそのまま値設定
shortTermFactorField.SetValue(__instance, foo);
// (B) ILGenerate した Setter の Delegate を 呼んで値設定
setShortTermFactor(__instance, bar);
}
}
多分こんな感じになります。適当に書いてるのでビルド通るかどうかもわかりませんけど。
A か B のどちらか一方でいいですが、おすすめはBですね。Reflectionは重いので、値の取得/設定自体はILGenerate(Harmony.FastAccess)した方が100倍以上速いです。

> SR 5以下の場合増加、5以上の場合スルー
Postfixなので、本物のRampUpWatcherTickが呼ばれた後に呼ばれますよ。
なので、本物のRampUpWatcherTickで値の増加が行われた後でPostfixが呼ばれるので、スルーというよりPostfixでは値を戻すようにするか
Prefixで、本物のRampUpWatcherTickを呼ばれないようにするかしないとですかね。

5プログラム初心者 ◆l3Flv.uj/A:2018/08/17(金) 06:03:30
>>4
ありがとうございます!

教えて頂き恐縮なのですが
理解するのに時間が掛かりそうです…
今はブラックボックス的な使い方しかできそうにないです
ですが、おかげで色々なクラスのメソッドを弄れそうです。

自分のプログラミング力が低く
再度お聞きする事になり申し訳ないのですが

(B)の方がおすすめとの事で(B)を試したのですが
'FastAccess'はアクセスできない保護レベルになっています。
とエラーが出てしまいます。

Postfixの場合
呼ばれて 5.000 += 増加分
var foo = field.GetValue(__instance);
…処理後
foo = 5; shortTermFactorField.SetValue(__instance, foo);
もしくは
…var bar = (float)getShortTermFactor(__instance);
…処理後
bar = 5; setShortTermFactor(__instance, bar);
で、再度5.000に戻るという感じで宜しいでしょうか?

Prefixで元のメソッドでスキップができればいいのですが
スキップの方法が分かりません…

(A)の方法でstatic void Postfixで試しに実行しましたが
値を極限に振っても(sr:10000 * ir:10000)効果が出ているのか
反映されているか(してない?)不明で
このmodは目に見えて分かるわけではないと気づきまして…
mod作成をする時に値を確認する方法
ShortTermFactor
LongTermFactor ("sr:" + sr + " , " + "ir:" + ir);
襲撃合計ポイント(計算式)
などの値をdebag Log等で見れるようにしてみたいのですがどうすればいいのでしょうか?

お聞きしてばかりで申し訳なく
調べてからと思い色々他のmodやソースを見ているのですが
全く引っ掛からなくて途方に暮れています…

6名無しさん:2018/08/17(金) 09:09:51
> 'FastAccess'はアクセスできない保護レベルになっています。
HugsLibがどのバージョンのHarmony使ってるか知りませんが、Harmonyのバージョンが古いんですね。
https://github.com/pardeike/Harmony/commit/cac1bb391f129be8be18237ea9d3607cbc376400#diff-4cf05baf9c04ee551e5e41fcbbca4b8a
ここで、internal から public に変わってます。
V1.1.0以降であれば、使えるっぽいです。あとは、該当メソッドは10行くらいで書けるので自分で同じようなの書くかですね。
https://github.com/pardeike/Harmony/blob/master/Harmony/Extras/FastAccess.cs

> で、再度5.000に戻るという感じで宜しいでしょうか?
たとえば、5以上にはしたくないってだけであれば、Postfix呼ばれた時点では増加分まで入っているので
Postfix内で値を取得して5超過なら5に上書き設定してしまうとか。
if((float)getShortTermFactor(__instance) > 5f)
setShortTermFactor(__instance, 5f);
みたいな感じで。

> Prefixで元のメソッドでスキップが
https://github.com/pardeike/Harmony/wiki/Patching
一番下に
// prefix
// - wants instance, result and count
// - wants to change count
// - returns a boolean that controls if original is executed (true) or not (false)
static bool Prefix(Customer __instance, List<string> __result, ref int count)
って書いてあるので、戻り値で制御できるのではないですかね。これもバージョンによるかもしれませんけど。。。
元メソッドの中身を見てないのですけど、ほかの処理も入っていたらそれもスキップしてしまうので、注意してください。

> などの値をdebag Log等で見れるようにしてみたいのですがどうすればいいのでしょうか?
楽なのは、Verse.Log.Message, Verse.Log.Warning, Verse.Log.Error あたり使ってログ出すですかね。
rimworld を開発者モードにして、上の方に出てくるボタンで開けるログウィンドウに出力されます。
それより、実際に時間経過毎に襲撃来ないと設定した値が襲撃に反映されてるか判断できないのが辛そうですね。
開発者モードで時間経過とか襲撃発生で判断できるのかなぁ。。。

7プログラム初心者 ◆l3Flv.uj/A:2018/08/18(土) 09:55:35
>>6
お世話になります!
Harmonyを最新に変えたらFastAccessできました。

ログウィンドウを開いた時に表示させるようにしました。
float test = 123;
Log.Message("test :" + test);
と書いたら、デバックログに123と表示されたのでたぶん大丈夫だと思います。

ただの値を見るだけのstatic void postfixメソッドを作り
そして開発者モードで1season進めるコマンドあったので3年位進めて
5000Tickを何回か繰り返してwindowログを開いたら

public void showSrIr(){
Log.Message("sr1 :" + test1);
Log.Message("ir2 :" + test2);
}

sr1:0
sr2:0

と表示されました…デフォルトで1fのはず…

StoryWatcher_RampUpクラスに
public float ShortTermFactor{get{return this.shortTermFactor;}}
public float LongTermFactor{get{return this.longTermFactor;}}

あったので、念の為これでも見ましたが同じでした。
test1 = __instance.ShortTermFactor;
test2 = __instance.LongTermFactor;
実際に21、42日以降を検証してからまた報告します。

Harmonyの方は未だブラックボックスですが
Harmonyを使っている方のmodを見て値変更していそうな方のソースを見てきます

8プログラム初心者 ◆l3Flv.uj/A:2018/08/22(水) 20:19:55
Rimworld B18
net35 Lib.Harmony.1.2.0.1を使用してます。

modを改変しながら毎回初期プレイで21、42日の繰り返しをしていたので遅れました。
あれから色々と試した結果
21日と42日超えてから「5000tick」毎に判定されるので、
デバックによる1Season飛ばしでは飛ばした日数分のirとsr値は上がらないという事が分かりました。
(日数は判定されました)

デバックモードで、虫眼鏡っぽいやつの「Visibility」から「Write Storyteller」で
Tick、Storytellerの人、numRaidsEnemy(襲撃回数)、TotalThreatFactor、ShortFactor、LongFactorが見れる。
これにより正確に現在のTotalThreatFactor、ShortFactor、LongFactorが分かる。
(また、ストーリーテラーの襲撃頻度も分かる)

21日超えてから5000tickで0.000356
42日超えてから5000tickで0.000231上がり出した事から「何らか」のメソッドは正常に動いている

別のクラスのメソッドと「publicなフィールド」で適当に試したら
[HarmonyPatch(typeof(クラス名))]
[HarmonyPatch("メソッド名")]
public class Patch {
static void Postfix(クラス名 __instance) {
__instance.フィールド名 = 「値」;
}
}
で、フィールド名の値が「値」に書き換わっているのがデバックログから確認できました。

また、別のクラスとメソッドのPostfixメソッド内(上記)でLog.Massage("適当");
とすれば、ログに適当と書かれるのですが
Harmonyを用いてRampUpWatcherTickメソッドの後に呼ばれるPostfix内にLog.Massage("適当");
を入れても、5000tickで「適当」と表示されない事から…
このメソッド(インスタンス?)ゲーム内で呼ばれてないんじゃない?とか疑問に思いました。
(前回は別メソッド内でLog.Massageを見ていた、今回は直接メソッド内に書き込んだ)

setShortTermFactor(__instance, bar);でprivateのフィールド変更についても
デバックモードの虫眼鏡で見ても反映されませんでした…(動作もしない?のでLog.Massageで値すら見れない)

適当なコンソールプログラムでリフレクション等も少し齧り
privateの値を適当に変更する等学び
同じ要領でStorywatch_RampUpメソッドのフィールドを変更しても同じでした…
今も色々と弄っていますが完全に手詰まりな状態です…

そもそものメソッドが呼ばれないのなら…値が変わらないのも…
これから別のクラスの適当なメソッドでprivate変数を変えられるか試していきます。

9名無しさん:2018/08/22(水) 21:35:37
ちょろっと中見て試してみましたけど、RampUpWatcherTick の Postfix 呼ばれましたよ。
あと、5000tickに1回呼ばれるのではないですよ。
Storywatch_RampUp は毎Tick呼ばれて、その中で値を更新するのが5000Tickに1回なだけなのでPostfixも毎Tick呼ばれます。
そして↑見て気付いたのですけど、"PostFix"ではなく、"Postfix"ですね。fは小文字ですね。
なので、シグネチャ合ってなくて呼ばれないだけではないのでしょうか。
[StaticConstructorOnStartup]
[HarmonyPatch(typeof(StoryWatcher_RampUp), "RampUpWatcherTick")]
class Patch_StoryWatcher_RampUp
{
static Patch_StoryWatcher_RampUp()
{
HarmonyInstance.Create("Nullre.TestMod").PatchAll(Assembly.GetExecutingAssembly());
}

static void Postfix(StoryWatcher_RampUp __instance)
{
Log.Message("ShortTermFactor : " + __instance.ShortTermFactor);
}
}
まぁ、リフレクションとか動的とか解決するに動かす場合によくやるミスですね。静的ならコンパイルエラーで一発で分かりますからね。

10名無しさん:2018/08/22(水) 21:41:33
> まぁ、リフレクションとか動的とか解決するに動かす場合によくやるミスですね。静的ならコンパイルエラーで一発で分かりますからね。
まぁ、リフレクションとかで動的に名前解決する場合によくやるミスですね。静的ならコンパイルエラーで一発で分かりますからね。

怪しい日本語ですみません。。。

11プログラム初心者 ◆l3Flv.uj/A:2018/08/22(水) 22:13:24
>>10
あ……ほんとだ、StoryWatcher_RampUpのだけPostFixになっていました
他は全部PostfixになっていたのにそれだけPostFixに…
別クラス別メソッド(public field)でやった時はPostfixになっていたので出来たのですね
直したらLog.Message動きました

いやはやお恥ずかしい

もう一度やり直してきます!

12プログラム初心者 ◆l3Flv.uj/A:2018/08/24(金) 03:47:23
privateのフィールドの書き換えできました!
初めてC#でまともなmodを作れました
ほぼ答えを教えて頂きましたが…
後は調整と改良していこうと思います

また、色んな方のmodの中身を見させて頂き
どのクラスがどんな作用をしているのか徐々に理解出来てきたので
別のmodも作成してみようと思います

13プログラム初心者 ◆l3Flv.uj/A:2018/08/24(金) 05:42:03

Harmonyの英wikiの翻訳を読んでいて___someFieldを使用するとインスタンスフィールドの
読み書きができるみたいですね(スリーアンダースコア)
その時はPost「F」ixだった為に…反映されませんでしたが…

void型ではないのも__resultで戻り値にアクセスできるので
色々とmod作成が捗る…

static void Postfix(StoryWatcher_RampUp __instance,ref float ___shortTermFactor,ref float ___longTermFactor)
{
___shortTermFactor = 8;
___longTermFactor = 8;
}

__instance __result __state ___someField __originalMethod

もうちょっと英wikiの方の熟読が必要かもですね…
辞書抱えて翻訳してきます

14プログラム初心者 ◆l3Flv.uj/A:2018/08/24(金) 06:30:47
namespace Test
{
[StaticConstructorOnStartup]
class Main
{
static Main()
{
var harmony = HarmonyInstance.Create("com.test.fixedvalue");
harmony.PatchAll(Assembly.GetExecutingAssembly());
}
}

class Flag {
public static bool flag = true;
}

[HarmonyPatch(typeof(StoryWatcher_RampUp))]
[HarmonyPatch("RampUpWatcherTick")]
class StoryWatcher_RampUp_RampUpWatcherTick_Patch
{
static bool Prefix(ref float ___shortTermFactor,ref float ___longTermFactor)
{
if (Flag.flag == true)
{
___shortTermFactor = 8;
___longTermFactor = 8;
Flag.flag = false;
}
return true;
}
}
}

一回だけsr,ir値を8にして以降何もしない。その後、
最後のreturn をtrue で元のメソッドを実行 sr = 8.000356...
最後のreturn をflaseで元のメソッドをスキップできました sr = 8
これでHarmonyをたぶん理解できたと思います!
本当にありがとうございました

15名無しさん:2018/08/25(土) 01:27:18
おめでとうございます!
ぜひ、ある程度形になったら公開してみてください。

> Harmonyの英wikiの翻訳を読んでいて___someFieldを使用するとインスタンスフィールドの
> 読み書きができるみたいですね(スリーアンダースコア)

これは知りませんでした。勉強になりました。
あと、面倒な方法教えちゃってすみませんでした。


新着レスの表示


名前: E-mail(省略可)

※書き込む際の注意事項はこちら

※画像アップローダーはこちら

(画像を表示できるのは「画像リンクのサムネイル表示」がオンの掲示板に限ります)

掲示板管理者へ連絡 無料レンタル掲示板