- Backup your
Lightning.exe
. These tools won't damage it, but you might - rename it to something likeLightningBackup.exe
. - Compile
Opus Modded
(produces mapped & patched decompiled output) andOpusPatcher
(helps dump scrambled strings). Copymappings.csv
,keys.txt
, andpatch.diff
fromOpus Modded
to somewhere else, you'll need those. - Run
OpusPatcher.exe
, passing the path to yourLightning.exe
as the first command line argument, and the path to yourkeys.txt
as the second. It creates a newLightning.exe
in the directory you ran it - move it to the normal Opus Magnum folder and run it. It must be namedLightning.exe
, and I think it has to be in the correct folder (though I haven't tested). It won't start the game, instead creatingout.csv
. Avoid overwriting your originalLightning.exe
. Don't worry if you do - Steam can redownload it. It's just annoying. - Run
Opus Modded.exe
, passing the path to yourLightning.exe
as the first argument and path tomappings.csv
a
Now with 100% less recompiling! And 100% more downloading. Sorry.
Download the following:
-
The two executables (Opus Patcher, Modded Opus), which you can get from the releases on their respective repos (on my GitHub profile). -The former runs on .NET Framework, which is required to patch with Opus Magnum's executable, and the latter runs on .NET Core, which has libraries I rely on.
-
Mono.Cecil
, available from NuGet (https://www.nuget.org/). If you're unfamiliar with NuGet and simply want to play, download the package using the link on the sidebar, rename it from a .nupkg to a .zip, extract it to a folder, and copyMono.Cecil.dll
from thenet40
directory.Mono.Cecil
provides tools for patching an executable, used to extract strings from the game.
At the start of a cycle,
- Grab and Drop instructions are executed
- Glyphs are updated (start of cycle)
- All other instructions are executed
- Collisions are checked
At the end of a cycle,
- Glyphs are updated (end of cycle)
- Area is measured
- The cycle count is updated
# blah blah | |
# map metadata stuff idk | |
# all of this stuff is defined per-side and doesn't affect other sides | |
# if this isn't present, hearts use their regular appearance. | |
Hearts: # A list of hearts. An index parameter is added to crystal hearts to pick the right heart. Heart 0 is used for the chapter panel. This allows for multiple seperate hearts, like Shade World's golden heart but with a poem, or GMHS's fake heart (but with a fake poem too). | |
- Preset: "a_side" # For using a heart specified in code. Everest specifies the "a_side", "b_side", "c_side", and "farewell" hearts, and mods can add extras (e.g. "d_sides_graphics_pack:d_side"). | |
- Poem: "some_cool_poem" # Alternate hearts need to specify their poems (unless they want the same name). If empty, no text will appear. | |
GuiSprite: "heartgem_yellow" # Idk the actual name, but this points to an entry in XML, used for the poem UI and panel UI (if heart 0). Slightly less easy than in ASH, but more flexible. | |
InGameSprite: "heartgem_yellow" # Same as above, bu |
This proposal is not intended to add support for general-purpose custom initializer syntax for any kind of object, nor is it intended to allow for custom DSLs in Java. This is for simple shorthand:
Map<String, Object> values = { "a": "alpha", "b": "beta", null: this };
var list = ArrayList<String> { "a", "b", "c" };
Set checked = HashSet<> { NORTH, SOUTH, NORTHWEST, };
import Data.Binary.Get | |
import qualified Data.ByteString.Lazy as BSS | |
import Data.Bits (Bits((.|.), (.&.), shiftL)) | |
import Data.Text.Encoding (decodeUtf8) | |
import Data.Text (unpack) | |
import qualified Data.Map as M | |
import Data.Char (chr) |
{-# LANGUAGE OverloadedRecordDot #-} | |
import Data.List (sortBy, isInfixOf) | |
import Data.Ord (comparing) | |
import Data.Array ((!), bounds, listArray, Array) | |
suffixes :: Int -> [a] -> [(Int, [a])] | |
suffixes idx [] = [] | |
suffixes idx l@(_:xs) = (idx, l) : suffixes (idx + 1) xs |
{-# LANGUAGE LinearTypes #-} | |
{-# LANGUAGE TypeFamilies #-} | |
{-# LANGUAGE FunctionalDependencies #-} | |
import Data.Kind (Type, Constraint) | |
-- an unconstrained dynamic type is "inaccessible", and we'll use it to throw away values we don't need | |
data Bin where | |
Inaccessible :: a %1 -> Bin |
{-# LANGUAGE LinearTypes #-} | |
{-# LANGUAGE DataKinds #-} | |
import GHC.Base (Multiplicity(Many)) | |
data Bang a where | |
Bang :: a %Many -> Bang a | |
data With a b where | |
With :: u %1 -> (u %1 -> a) %Many -> (u %1 -> b) %Many -> With a b |
// util functions | |
function newLineFixup(text){ | |
return text.replace(/\r\n/g, "\n"); | |
} | |
function projectRoot(){ | |
var filePath = studio.project.filePath; | |
return filePath.substr(0, filePath.lastIndexOf("/")) + "/"; | |
} |