Modding Resources, tips and tricks!

Ole Marius Myrvold

JWB 96-13
Club Staff
Premium
Hi all! :)

As it is a new game, and a slightly different genre and build that most of the games on this site, there might be some newfound tricks to make modding faster, easier and/or better.
Personally I have more than enough to even try to understand how I might be able to mod the game, but I know that many of you guys are well underway with some cracking mods!
Anyway, sharing is caring, and making each other better is only positive in the long term, who knows what me might be able to achieve with this game! It is also easier to find for new modders, or old modders finding a new game if we are able to gather the brilliant research from you guys on one place :)

Keep up the magnificent work!

@thrashersfreak @Panamera4 @Colin O Callaghan @NemanjaSipka @TheFlamingRed @Mojojo999
 
Had a quick look through the .dll file mentioned in an earlier post. The relegation/promotion "script" should be editable.

Great stuff - the relegation/promotion might "ruin" the actual history, but it is a good way to bring Paul Stewart Racing to F1 yourself, or maybe even bring Pacific back to F1 from F3000 (taking 1996 as a base) ^^,

Oooh the possibilities are endless. I really got to learn this stuff :)
 
i m gonna keep vanilla game for now, its a bit of work changing things in game, and for mods that exists, they need some work too, im just gonna hope they will add some edit tools to the game so its easier for everyone..
 
You can with another tool: https://github.com/sailro/Reflexil/releases
Download "reflexil.for.ILSpy.2.1.AIO.bin.zip" and place the DLL in the same directory as ILSpy.
It will be extremely tedious though, because you have to replace the intermediate language code manually and can't just write CSharp code. Also if I understand Unity correctly (I have zero experience with it until now) a lot of values gets serialized/saved in your save game so if you make an edit using Reflexil to a default value it may get overwritten during loading from a save. This makes just injecting an assembly superior, because you can alter values in-memory and not worry about IL or stuff.



The stats for backstories are hard-coded, see image:
l1R4yTI.png


They are serialized to the savefile, so in theory you could change the savefile to change these stats. Here's a quick python utility to read savefiles, be warned these saves are like 32 megabytes of json data:thumbsdown:. You may want to use a hex editor like HxD to traverse these. You will need to pip install lz4.
Code:
import struct
import json
import lz4


with open(r"Crashed - Octane Racing.sav", "rb") as f:
    magic = struct.unpack("i", f.read(4))[0]
    version = struct.unpack("i", f.read(4))[0]

    header_insize = struct.unpack("i", f.read(4))[0]
    header_outsize = struct.unpack("i", f.read(4))[0]
    data_insize = struct.unpack("i", f.read(4))[0]
    data_outsize = struct.unpack("i", f.read(4))[0]

    header = f.read(header_insize)
    header = lz4.decompress(struct.pack("i", header_outsize) + header)
    header = json.loads(header.decode("utf-8", "ignore"))

    data = f.read(data_insize)
    data = lz4.decompress(struct.pack("i", data_outsize) + data)
    data = json.loads(data.decode("utf-8", "ignore"))


print(magic, 1932684653)
print(version)
print("header_insize", header_insize)
print("header_outsize", header_outsize)
print("data_insize", data_insize)
print("data_outsize", data_outsize)

with open("header.json", "w") as f:
    f.write(json.dumps(data))

with open("data.json", "w") as f:
    f.write(json.dumps(data))

print(data.keys())
I managed to get home from work a little earlier then expected, so began (stress that word) to look at the dll.

Noticed that 1. It looks like they've already got whats going to be modable and no mostly set. From the looks of it, its going to be mostly a cosmetic workshop. Models, Textures, Names, Logos,Car Parts (I assume starting stats), portraits, sponsors etc. So Those wanting to make Real World / History / Fantasy mods who don't want to do it the hard way, will be very happy to once that workshop opens. :p Unfortunatly, I think there will probably be 100 F1 mods within a few weeks of that launching though.

2. It doesn't look like the workshop is going to add any functionality to edit the gameplay at all. Beyond the Easy "Game Data" in the asset folder (which deals with day to day balacing, tyres life, race lengths, differences between best and worst data, etc, etc, it looks like modifying gameplay is going to be far easier using an injector (unless someone wants to make a utility to which gets around the 'serialization and intermediary code' problem (Maybe we should start sending money to Ryder :p)?

It probably means that we don't need to to bother waiting for the Workshop to begin some basic gameplay changes. It is probably worth while waiting for at least the first patch for some fixes though, as that should soon be on its way.

3. I have only looked at a couple of percent of the things available in it... I saw from the asset files just how much was cut: i.e., Pre-race talks to your drivers, Advanced Setups with things like camber effecting your tyre wear, Mechanic and Designer moral. But in the dll it shows even more abandoned things: i.e., Young Driver Programs, Factory Working Hours

Also, you can kill your enemies

Code:
public class GameScore : MonoBehaviour
{
    private static GameScore s_Instance;

    public string playerLayerName = "Player";

    public string enemyLayerName = "Enemies";

    private int m_Deaths;

    private readonly Dictionary<string, int> m_Kills = new Dictionary<string, int>();

    private float m_StartTime;

    private static GameScore Instance
    {

4. I assume they're going to sort out the qualifying blocking bug. But it looks like there is adaquate things in the dll to adjust the AI racing line behaviour during an in and outlap... That;s going to be awkward though - so hoping that's something the devs will do.

5. There is so much potential :p yet so much to decypher. Suppose I had better learn how to make injectors and start experimenting.



...





 
Ye, please don't misunderstand, it is somewhat expected that there are small mistakes when the mods are made :) The game is going to be patched, and they get paid for it. So no wonder that free stuff might need a patch or two as well :)

In non technical words, it's a 'side program' which runs alongside a 'main program'. The side program actively overrides code which occur in the 'main program' externally.

I have only seen a few of these - so I don't know the ins and outs. I was first introuced to it in Skyrim ERBS. The Injector overhauled the graphics and shading of the base game.

Side note:

Thought it maybe interesting to analyse some AI Race Strategy... This is a subset of the code which (a) is for the AI set to be least aggressive, (b) That includes tyre wear consideration and (c) sets the AI to attack - Being faster - but increasing wear

Code:
251,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,LapsToGo < 2; TyreWear > 0.4; TyreTemperature  < 0.7,DrivingStyle = Attack,Strategy,Not used\r
269,TRUE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,TyreTemperature  < -0.95; AirTempTyreHeatingRate < 0; GapToBehind < 2,DrivingStyle = Attack,Strategy,Not used\r
278,TRUE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,TyreWear < 0.3;  GapToBehind > 10; TyreTemperature < 0.1; AirTempTyreHeatingRate < 0; GapToBehind < 2,DrivingStyle = Attack,Strategy,Not used\r
299,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,LapsToGo > 10; TyreWear > 0.4; SessionPosition = 1; GapToBehind < 1; TyreTemperature  < 0.7,DrivingStyle = Attack,Strategy,Not used\r
318,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,GapToLeader < 5; LapsToGo > 10; TyreWear > 0.4; SessionPosition != 1; GapToBehind < 2; TyreTemperature  < 0.7,DrivingStyle = Attack,Strategy,ID:258 _used 6 times; 1 %\r
342,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,LapsToGo < 2; TyreWear > 0.4; GapToAhead < 1; TyreTemperature  < 0.7,DrivingStyle = Attack,Strategy,Not used\r
474,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,GapToPredictedPosition > 3; TyreTemperature  < 0.7; TyreWear > 0.6,DrivingStyle = Attack,Strategy,Not used\r
481,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,GapToAhead < 3; TyreTemperature < 0.7; TyreWear > 0.5,DrivingStyle = Attack,Strategy,Not used\r
494,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,LapsToGo < 3; TyreTemperature < 0.7; TyreWear > 0.6,DrivingStyle = Attack,Strategy,Not used\r
500,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,LapsToGo < 2; TyreTemperature < 0.7; TyreWear > 0.5,DrivingStyle = Attack,Strategy,Not used\r
541,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,SessionCompletion < 0.4;SessionCompletion > 0.2; TyreTemperature < 0.55,DrivingStyle = Attack,Strategy,Not used\r
600,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,SessionCompletion > 0.8; TyreTemperature < 0.6; TyreWear > 0.5,DrivingStyle = Attack,Strategy,Not used\r
617,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,SessionCompletion > 0.8; TyreTemperature < 0.6; TyreWear > 0.3; LapsToGo < 2,DrivingStyle = Attack,Strategy,Not used\r
695,FALSE,SessionType = Race; TeamAggression < 0.26; SessionFlag = Green,SessionCompletion > 0.8; TyreTemperature < 0.6; TyreWear > 0.4; TyreWear < 0.5; GapToPredictedPosition > 0,DrivingStyle = Attack,Strategy,Not used\r

Comparing that to my normal strategies... They're attacking a lot, and this is the least aggressive AI. They burn up their tyres so much, I always find it easy to run longer into clean air - and clean air > the undercut....

So... I could try and change the (460) lines of the AI strategy's... or maybe a solution, would be to Reduce the Tyre wear on attack... Reduce the time gained from attack... Increase time time differential between New Tire, Part Worn and Really Worn tires, and Off the Cliff tires (tires are seperated into these states in a different section).

I feel this may benefit the AI more - Any opinions?
 
Last edited:
5. There is so much potential :p yet so much to decypher. Suppose I had better learn how to make injectors and start experimenting.

You already have one! The payload is in mm2h.dll, you can replace this with your own payload. You will need a CSharp compiler, I use Visual Studio 2015 the free edition for this. You need a class called "mm2hack" with a static method named "init" this is the entry point the injector calls. So a minimal example:

Code:
public class mm2hack {
    static void init() {
        // we're in!
    }
}

To compile this you want to link to the game's assemblies, so the command would be something like:
Code:
csc /pathtomm/MM_Data/Managed/Assembly-CSharp.dll /path/etc/Assembly-CSharp-firstpass.dll /path/etc/UnityEngine.dll /path/UnityEngine.UI.dll /path/mscorlib.dll /nostdlib my_file.cs

Make sure your resulting DLL is named "mm2h.dll" and replace the other one with it. Now your payload will be injected. Here's the one I'm currently using in my game, which allows you to zoom in and out much further in a race and makes the 3x speed considerably faster.

Code:
using UnityEngine;
using System.Reflection;

public class mm2hack : MonoBehaviour {
    App app;
    Game game;
    GameState.Type state;

    static void init() {
        GameObject obj = new GameObject();
        obj.AddComponent<mm2hack>();
    }

    void Awake() {
        app = App.instance;
        game = Game.instance;
        state = GameState.Type.None;
        GameStatsConstants.minTweetValue = 16;
        GameStatsConstants.maxTweetValue = 32;
    }

    void Update() {
        if(app.gameStateManager.currentState.type != state) {
            state = app.gameStateManager.currentState.type;
            OnStateChange();
        }
    }

    void OnStateChange() {
        System.Console.WriteLine("State changed to: " + state.ToString());
        ConsoleCommands.useAIForDrivers = false;

        switch(state) {
            case GameState.Type.StartUp:
                if(app.database == null)
                    app.LoadGameDatabases();

                app.gameStateManager.LoadToTitleScreen();
                break;

            case GameState.Type.FrontendState:
                int index;
                for(index = 0; index < game.teamManager.count; index += 1) {
                    Team team = game.teamManager.GetEntity(index);
                    //team.carManager.carCount = 3;
                    //team.mainDriverCount = 3;
                    System.Console.WriteLine("Team[" + index + "] = " + team.name);
                }
                break;

            case GameState.Type.PracticePreSession:
            case GameState.Type.QualifyingPreSession:
            case GameState.Type.RacePreSession:
            case GameState.Type.SkipSession:
                game.stateInfo.GoToNextState();
                break;

            case GameState.Type.PracticePostSession:
            case GameState.Type.QualifyingPostSession:
                app.gameStateManager.OnContinueButton();
                break;

            case GameState.Type.RaceGrid:
            case GameState.Type.Race:
                ConsoleCommands.useAIForDrivers = true;
                FreeRoamCamera camera = app.cameraManager.gameCamera.freeRoamCamera;
                camera.GetType()
                    .GetField("minZoom", BindingFlags.NonPublic | BindingFlags.Instance)
                    .SetValue(camera, 20f);
                camera.GetType()
                    .GetField("maxZoom", BindingFlags.NonPublic | BindingFlags.Instance)
                    .SetValue(camera, 2000f);
                camera.GetType()
                    .GetField("minRotationX", BindingFlags.NonPublic | BindingFlags.Instance)
                    .SetValue(camera, 2f);
                camera.GetType()
                    .GetField("maxRotationX", BindingFlags.NonPublic | BindingFlags.Instance)
                    .SetValue(camera, 88f);
                float[,] speedMultipliers = game.time.GetType()
                    .GetField("speedMultipliers", BindingFlags.NonPublic | BindingFlags.Instance)
                    .GetValue(game.time) as float[,];
                speedMultipliers[2, 2] = 10f;
                break;
        }
    }
};
 
Last edited:
On the injector side I will release the source once I can get unloading assemblies reliably working. With mono it should in theory be possible to unload your payload, which is quite unsafe if you have interacted with the game and have pointers pointing to your memory, but if you're careful with that you can unload the payload and reload a new one. This would be useful for quickly testing your program - so you don't have to close the game and relaunch the game everytime you make a change. I have yet to make this reliably work :(

Perhaps a solution is to make the payload assemblies have a unique name every time (so that they will be loaded and not used from an old cached version), but then the payloads will step on each others toes.
 
You already have one! The payload is in mm2h.dll, you can replace this with your own payload. You will need a CSharp compiler, I use Visual Studio 2015 the free edition for this. You need a class called "mm2hack" with a static method named "init" this is the entry point the injector calls. So a minimal example:

Code:
public class mm2hack {
    static void init() {
        // we're in!
    }
}

To compile this you want to link to the game's assemblies, so the command would be something like:
Code:
csc /pathtomm/MM_Data/Managed/Assembly-CSharp.dll /path/etc/Assembly-CSharp-firstpass.dll /path/etc/UnityEngine.dll /path/UnityEngine.UI.dll /path/mscorlib.dll /nostdlib my_file.cs

Make sure your resulting DLL is named "mm2h.dll" and replace the other one with it. Now your payload will be injected. Here's the one I'm currently using in my game, which allows you to zoom in and out much further in a race and makes the 3x speed considerably faster.

Code:
using UnityEngine;
using System.Reflection;

public class mm2hack : MonoBehaviour {
    App app;
    Game game;
    GameState.Type state;

    static void init() {
        GameObject obj = new GameObject();
        obj.AddComponent<mm2hack>();
    }

    void Awake() {
        app = App.instance;
        game = Game.instance;
        state = GameState.Type.None;
        GameStatsConstants.minTweetValue = 16;
        GameStatsConstants.maxTweetValue = 32;
    }

    void Update() {
        if(app.gameStateManager.currentState.type != state) {
            state = app.gameStateManager.currentState.type;
            OnStateChange();
        }
    }

    void OnStateChange() {
        System.Console.WriteLine("State changed to: " + state.ToString());
        ConsoleCommands.useAIForDrivers = false;

        switch(state) {
            case GameState.Type.StartUp:
                if(app.database == null)
                    app.LoadGameDatabases();

                app.gameStateManager.LoadToTitleScreen();
                break;

            case GameState.Type.FrontendState:
                int index;
                for(index = 0; index < game.teamManager.count; index += 1) {
                    Team team = game.teamManager.GetEntity(index);
                    //team.carManager.carCount = 3;
                    //team.mainDriverCount = 3;
                    System.Console.WriteLine("Team[" + index + "] = " + team.name);
                }
                break;

            case GameState.Type.PracticePreSession:
            case GameState.Type.QualifyingPreSession:
            case GameState.Type.RacePreSession:
            case GameState.Type.SkipSession:
                game.stateInfo.GoToNextState();
                break;

            case GameState.Type.PracticePostSession:
            case GameState.Type.QualifyingPostSession:
                app.gameStateManager.OnContinueButton();
                break;

            case GameState.Type.RaceGrid:
            case GameState.Type.Race:
                ConsoleCommands.useAIForDrivers = true;
                FreeRoamCamera camera = app.cameraManager.gameCamera.freeRoamCamera;
                camera.GetType()
                    .GetField("minZoom", BindingFlags.NonPublic | BindingFlags.Instance)
                    .SetValue(camera, 20f);
                camera.GetType()
                    .GetField("maxZoom", BindingFlags.NonPublic | BindingFlags.Instance)
                    .SetValue(camera, 2000f);
                camera.GetType()
                    .GetField("minRotationX", BindingFlags.NonPublic | BindingFlags.Instance)
                    .SetValue(camera, 2f);
                camera.GetType()
                    .GetField("maxRotationX", BindingFlags.NonPublic | BindingFlags.Instance)
                    .SetValue(camera, 88f);
                float[,] speedMultipliers = game.time.GetType()
                    .GetField("speedMultipliers", BindingFlags.NonPublic | BindingFlags.Instance)
                    .GetValue(game.time) as float[,];
                speedMultipliers[2, 2] = 10f;
                break;
        }
    }
};

:) it's awesome to have an expert injector person on the team :)

I found a few videos last night about making injectors, so will have a lot bigger look at this all in the next few days.

But a greater zoom sounds awesome :) in terms of zoom in, I assume they had their current zoom level set as the detail in the textures used in races is probably quite low (a performance saving issue no doubt) - so I assume it doesn't look as good - but as I am a gameplay > Athestics person, that probably wouldn't bother me as much...

Meanwhile, I think we also have someone new in the forums who specialises in models and textures,,, so there's a chance we can overhaul the textures / models to be of a higher quality if it was a project which was of interest - which would accompany the zoom in feature well.

I am sure somewhere, there is a variable for the current car direction, which could be fitted with the camera direction. I often, when viewing close up (as default will allow) rotating my camera manually to be behind the car as it moves around the track, like the common 'follow cam' in the f1 games. It's probably possible to actually add that in using an injector :)
 
Hello guys. I've downloaded F1 Reality-mod and I'm trying to do little modifications on it. I'm trying to switch Kvyat's and Verstappen's places so they would match the current situation, but whenever I load the game, everyone's names have changed to single dot (see the picture). I have tried this in many ways and it occurs even if I just edit names of those drivers, I don't touch any other information (Max,Verstappen, -> Daniil,Kvyat). Can someone explain why this happens?
20161120040721_1.jpg
 
Hello guys. I've downloaded F1 Reality-mod and I'm trying to do little modifications on it. I'm trying to switch Kvyat's and Verstappen's places so they would match the current situation, but whenever I load the game, everyone's names have changed to single dot (see the picture). I have tried this in many ways and it occurs even if I just edit names of those drivers, I don't touch any other information (Max,Verstappen, -> Daniil,Kvyat). Can someone explain why this happens?View attachment 160603
dont have a clue personally. Upload the asset file somewhere and we can have a look and see.
 
e55crashed you are a genius! Great and impressive work.

There were some questions about how to add an additional team. I succeeded in adding an 11th team for the single race mode. These files are the minimum files to change for: Chairman, Championships, Default Parts, Drivers, HQ, Mechanics, SingleSeaterDesignData (check for all TimeCostForRank tags and add the missing ones), SingleSeaterDesignDataDefault (I'm not sure if this file really requires some changes), Team AI Weightings and Teams.
I know there are some visual limitations with this 11th team, but it was just a proof of concept and I didn't care about visualization yet.

However when it comes to career mode, there is a CTD at the home screen when you hover about the team data stats. Problem is that the TeamStatsRollover class has an array of TeamStatsEntries with a length of 10 instead of 11. I'm not familiar with Unity, but I assume that the class intance is filled with data from a prefab (?). Unfortunately I wasn't able to find and modify these prefabs yet.
Another way would be to inject code as e55crashed proposes. I will try to inject an additional TeamStatsEntry (via GameObject creation), but I'm not sure if this helps.

Keep up your analysis and awesome work.

@e55crashed: by the way, where is the mm2h.dll you are talking about?
 
gpxricky: I posted it on Steam http://steamcommunity.com/app/415200/discussions/0/208684375413566748/

It has a general-purpose assembly injector for MM, just replace mm2h.dll with your own payload. You could search on the internet for other injectors it doesn't have to be mine. Search something like "Unity injector" but then you're on your own. I'm working on an updated version that I'll share the code for but progress is slow, it is quite prone to crashing when I want to unload the payload. Reading the code to these other injectors shows they do not attempt to unload, I guess this is the route I'll have to take.
 
Adding an existing team works, but:

-CTD happens when: hovering over any of the team standings, in championship mode.
-When going to the track, after initial screen it crashes.

gpxricky: What did it look like when you entered the track? Was the game able to assing the 11th team a pitbox and a spot on the starting grid? Perhaps you could send me your resources files so i can have a look?
 
Looks like the AI actually use cash, and manage it like a human player would. Which causes an issue, as I was hoping that I could change some cash value of things and the AI would ignore it and play as normal... but the AI is a bit smarter then I thought.

The biggest problem with balancing the game - is the fact that any chances I am making to nerf the player, is also potentially nerfing the AI :p
 
Alright I have a question and I do it here since there is no sub_forum for questions yet. Do you have to start a new game when editing files? I am far into this game now and would like to use my old save game but it seems that it doesn't work that way, is there a way to work around it?
 

Latest News

Do you prefer licensed hardware?

  • Yes for me it is vital

  • Yes, but only if it's a manufacturer I like

  • Yes, but only if the price is right

  • No, a generic wheel is fine

  • No, I would be ok with a replica


Results are only viewable after voting.
Back
Top