~zanneth/SolitaireOracle

b8e6b30a557e1f47bf0b397a7f669b6eca469528 — zanneth 1 year, 10 months ago 96eda13
Implement code to read the rest of the board state
M InjectedLib/dllmain.cpp => InjectedLib/dllmain.cpp +0 -7
@@ 43,7 43,6 @@ static std::wstring inject_asm_path(void)
static void load_managed_assembly(void)
{
    ICLRMetaHost *rt_meta = nullptr;
    IEnumUnknown *rt_itr = nullptr;
    IUnknown *rt_variant = nullptr;
    ICLRRuntimeInfo *rt_info = nullptr;
    HRESULT hr;


@@ 53,12 52,6 @@ static void load_managed_assembly(void)
        show_err(L"Failed to create instance of runtime meta host.", hr);
        return;
    }
    
    hr = rt_meta->EnumerateInstalledRuntimes(&rt_itr);
    if (FAILED(hr)) {
        show_err(L"Failed to enumerate installed runtimes.", hr);
        return;
    }

    hr = rt_meta->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&rt_info));
    if (FAILED(hr)) {

A InjectedManagedAssembly/BoardState.cs => InjectedManagedAssembly/BoardState.cs +68 -0
@@ 0,0 1,68 @@
using System.Collections.Generic;
using System.ComponentModel;

namespace SolitaireOracle
{
    /* The associated values are consistent with the game. */
    public enum Suit : int
    {
        [Description("S")]
        Sword = 0,

        [Description("C")]
        Cup = 1,

        [Description("T")]
        Star = 2,

        [Description("W")]
        Wand = 3,

        [Description("M")]
        Major = 4,
    }

    public struct Card
    {
        public Suit Suit { get; }
        public int Value { get; }

        public Card(Suit suit, int value)
        {
            Suit = suit;
            Value = value;
        }

        public override string ToString()
        {
            return Suit.ToString() + Value;
        }
    }

    public struct BoardState
    {
        public List<List<Card>> TableauCells { get; }
        public List<List<Card>> MinorFoundationCells { get; }
        public List<Card> LowerMajorFoundationCell { get; }
        public List<Card> UpperMajorFoundationCell { get; }
        public List<Card> FinalMajorFoundationCell { get; }
        public Card? FreeCell { get; }

        public BoardState(
            List<List<Card>> tableauCells,
            List<List<Card>> minorFoundationCells,
            List<Card> lowerMajorFoundationCell,
            List<Card> upperMajorFoundationCell,
            List<Card> finalMajorFoundationCell,
            Card? freeCell
        )
        {
            TableauCells = tableauCells;
            MinorFoundationCells = minorFoundationCells;
            LowerMajorFoundationCell = lowerMajorFoundationCell;
            UpperMajorFoundationCell = upperMajorFoundationCell;
            FinalMajorFoundationCell = finalMajorFoundationCell;
            FreeCell = freeCell;
        }
    }
}

A InjectedManagedAssembly/GameState.cs => InjectedManagedAssembly/GameState.cs +115 -0
@@ 0,0 1,115 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace SolitaireOracle
{
    public class MilanScreenNotVisibleException : Exception
    {
        public MilanScreenNotVisibleException() : base(
            "The \"Fortune's Foundation\" game screen is not active."
        )
        { }
    }

    public class GameState
    {
        private Assembly _assembly;
        private Type _screenStackType;
        private Type _gameScreenType;
        private Type _unknownControllerType;
        private Type _boardStateType;
        private Type _solitaireItemType;

        public GameState(Assembly assembly)
        {
            this._assembly = assembly;
            this._screenStackType = assembly.GetType("ScreenStack");
            this._gameScreenType = assembly.GetType("Milan.GameScreen");
            this._unknownControllerType = assembly.GetType("#=q0pk7ja$d6EGfjmD$pwqkUf_tDQr2NjwNEBTC6S3S6pw=");
            this._boardStateType = assembly.GetType("Milan.BoardState");
            this._solitaireItemType = assembly.GetType("Milan.SolitaireItem");
        }

        public BoardState BoardState
        {
            get
            {
                InternalBoardState internalState = InternalBoardState;

                List<List<Card>> tableauCells = new List<List<Card>>();
                List<InternalSolitaireItem> internalTableauCells = internalState.TableauCells;
                foreach (InternalSolitaireItem item in internalTableauCells)
                {
                    tableauCells.Add(item.Cards);
                }

                List<List<Card>> minorFoundationCells = new List<List<Card>>();
                List<InternalSolitaireItem> internalMinorFoundationCells = internalState.MinorFoundationCells;
                foreach (InternalSolitaireItem item in internalMinorFoundationCells)
                {
                    minorFoundationCells.Add(item.Cards);
                }

                List<Card> lowerMajorFoundationCell = internalState.LowerMajorFoundationCell.Cards;
                List<Card> upperMajorFoundationCell = internalState.UpperMajorFoundationCell.Cards;
                List<Card> finalMajorFoundationCell = internalState.FinalMajorFoundationCell.Cards;
                
                InternalSolitaireItem freeCellItem = internalState.FreeCell;
                Card? freeCell = null;
                if (freeCellItem.Cards.Count > 0)
                {
                    freeCell = freeCellItem.Cards[0];
                }

                return new BoardState(
                    tableauCells,
                    minorFoundationCells,
                    lowerMajorFoundationCell,
                    upperMajorFoundationCell,
                    finalMajorFoundationCell,
                    freeCell
                );
            }
        }

        private IList ScreenStack
        {
            get
            {
                MethodInfo getScreensMethod = _screenStackType.GetMethod("#=qwDu4tl5WqE$CaaOI6JbyYOihTs6X_cSn8YUJgM$9Kpc=", BindingFlags.Public | BindingFlags.Static);
                return getScreensMethod.Invoke(null, null) as IList;
            }
        }

        private object MilanGameScreen
        {
            get
            {
                IList screens = this.ScreenStack;
                object topScreen = screens.Count > 0 ? screens[0] : null;

                if (topScreen == null || topScreen.GetType() != this._gameScreenType)
                {
                    throw new MilanScreenNotVisibleException();
                }

                return topScreen;
            }
        }

        private InternalBoardState InternalBoardState
        {
            get
            {
                object gameScreen = MilanGameScreen;
                MethodInfo getControllerMethod = this._gameScreenType.GetMethod("#=qt9YAlgdQ0iozKkKJ_tz6wg==", BindingFlags.NonPublic | BindingFlags.Instance);
                object controller = getControllerMethod.Invoke(gameScreen, null);

                FieldInfo boardStateField = this._unknownControllerType.GetField("#=qggOaeeswRk1DSyXZZarAEQ==", BindingFlags.Public | BindingFlags.Instance);
                return new InternalBoardState(boardStateField.GetValue(controller), _boardStateType, _solitaireItemType);
            }
        }
    }
}

M InjectedManagedAssembly/InjectedManagedAssembly.csproj => InjectedManagedAssembly/InjectedManagedAssembly.csproj +3 -0
@@ 45,6 45,9 @@
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="BoardState.cs" />
    <Compile Include="GameState.cs" />
    <Compile Include="InternalBoardState.cs" />
    <Compile Include="Main.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>

A InjectedManagedAssembly/InternalBoardState.cs => InjectedManagedAssembly/InternalBoardState.cs +135 -0
@@ 0,0 1,135 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace SolitaireOracle
{
    public class InternalSolitaireItem
    {
        private object _internalState;
        private Type _type;

        public InternalSolitaireItem(object internalState, Type type)
        {
            _internalState = internalState;
            _type = type;
        }

        public List<Card> Cards
        {
            get
            {
                MethodInfo getIteratorMethod = _type.GetMethod("#=q3T5hVjfGdlmlcVuQneFNdGZgSGtulISaq7a1odM4P8Q=", BindingFlags.Public | BindingFlags.Instance);
                IEnumerable iterator = getIteratorMethod.Invoke(_internalState, null) as IEnumerable;
                List<Card> result = new List<Card>();

                foreach (object itemObj in iterator)
                {
                    InternalSolitaireItem item = new InternalSolitaireItem(itemObj, _type);
                    Card card = new Card(item.CardSuit, item.CardValue);
                    result.Add(card);
                }

                return result;
            }
        }

        private Suit CardSuit
        {
            get
            {
                FieldInfo field = _type.GetField("#=qrVeI19YFRrL$TYnUhjJkGg==", BindingFlags.Public | BindingFlags.Instance);
                return (Suit)field.GetValue(_internalState);
            }
        }

        private int CardValue
        {
            get
            {
                FieldInfo field = _type.GetField("#=qGa_rTwBFoHeQRSVVfaUd_Q==", BindingFlags.Public | BindingFlags.Instance);
                return (int)field.GetValue(_internalState);
            }
        }
    }

    public class InternalBoardState
    {
        private object _internalState;
        private Type _boardType;
        private Type _itemType;

        public InternalBoardState(object internalState, Type boardType, Type itemType)
        {
            _internalState = internalState;
            _boardType = boardType;
            _itemType = itemType;
        }

        public List<InternalSolitaireItem> TableauCells
        {
            get
            {
                FieldInfo field = _boardType.GetField("#=qzsOQ0cf1yO$i8Y9aA3nQSA==", BindingFlags.Public | BindingFlags.Instance);
                return GetCells(field.GetValue(_internalState) as IList);
            }
        }

        public List<InternalSolitaireItem> MinorFoundationCells
        {
            get
            {
                FieldInfo field = _boardType.GetField("#=qiTVSpq2PBgg8V050hDJn5oQb0VXwpTjrAZwWKRXR8hs=", BindingFlags.Public | BindingFlags.Instance);
                return GetCells(field.GetValue(_internalState) as IList);
            }
        }

        public InternalSolitaireItem LowerMajorFoundationCell
        {
            get
            {
                FieldInfo field = _boardType.GetField("#=qXTqemQdLoqSMR1UppWwmcud0M0zWMkRPfKUp$A23pQE=", BindingFlags.Public | BindingFlags.Instance);
                return new InternalSolitaireItem(field.GetValue(_internalState), _itemType);
            }
        }

        public InternalSolitaireItem UpperMajorFoundationCell
        {
            get
            {
                FieldInfo field = _boardType.GetField("#=qULFxwKOM5DOP9orpqK5SDiQQQPfH6OfkOPzcjwx4pdY=", BindingFlags.Public | BindingFlags.Instance);
                return new InternalSolitaireItem(field.GetValue(_internalState), _itemType);
            }
        }

        public InternalSolitaireItem FinalMajorFoundationCell
        {
            get
            {
                FieldInfo field = _boardType.GetField("#=q68Ek4QcjyWoJ0OYSV2IVI2L7fgwXBz$QyDNQuIS9AM0=", BindingFlags.Public | BindingFlags.Instance);
                return new InternalSolitaireItem(field.GetValue(_internalState), _itemType);
            }
        }

        public InternalSolitaireItem FreeCell
        {
            get
            {
                FieldInfo field = _boardType.GetField("#=qfmGFvCGS9zpXaXWksXqNSQ==", BindingFlags.Public | BindingFlags.Instance);
                return new InternalSolitaireItem(field.GetValue(_internalState), _itemType);
            }
        }

        private List<InternalSolitaireItem> GetCells(IList items)
        {
            List<InternalSolitaireItem> cells = new List<InternalSolitaireItem>();
            foreach (object item in items)
            {
                cells.Add(new InternalSolitaireItem(item, _itemType));
            }

            return cells;
        }
    }
}

M InjectedManagedAssembly/Main.cs => InjectedManagedAssembly/Main.cs +55 -88
@@ 1,6 1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;


@@ 13,108 14,74 @@ namespace SolitaireOracle
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool AllocConsole();

        private static readonly Assembly GameAssembly = Assembly.GetEntryAssembly();

        public static int Initialize(String arg)
        {
            AllocConsole();
            Console.WriteLine("injection assembly initialized");

            IList tableauCells = GetTableauCells();
            foreach (object cell in tableauCells) {
                foreach (object card in GetCellIterator(cell)) {
                    Console.WriteLine("suite = {0}, value = {1}", GetCardSuit(card), GetCardValue(card));
            try
            {
                GameState gameState = new GameState(Assembly.GetEntryAssembly());
                BoardState boardState = gameState.BoardState;

                Console.WriteLine("tableau =");
                foreach (List<Card> cell in boardState.TableauCells)
                {
                    foreach (Card card in cell)
                    {
                        Console.WriteLine(card.ToString());
                    }
                    Console.WriteLine("================================");
                    Console.WriteLine("");
                }

                Console.WriteLine("================================");
                Console.WriteLine("");
            }

            return 1337;
        }

        public static object GetGameLogic()
        {
            Type gameLogicT = GameAssembly.GetType("GameLogic");
            FieldInfo instanceField = gameLogicT.GetField("#=qScrhqyNr4nqz94n2Zvdliw==", BindingFlags.Public | BindingFlags.Static);
            return instanceField.GetValue(gameLogicT);
        }

        public static IList GetScreenStack()
        {
            Type screenStackT = GameAssembly.GetType("ScreenStack");
            MethodInfo getScreensMethod = screenStackT.GetMethod("#=qwDu4tl5WqE$CaaOI6JbyYOihTs6X_cSn8YUJgM$9Kpc=", BindingFlags.Public | BindingFlags.Static);
            return getScreensMethod.Invoke(null, null) as IList;
        }

        public static object GetMilanGameScreen()
        {
            Type milanGameScreenType = GameAssembly.GetType("Milan.GameScreen");
            IList screens = GetScreenStack();
            object topScreen = screens.Count > 0 ? screens[0] : null;

            if (topScreen == null || topScreen.GetType() != milanGameScreenType) {
                MessageBox.Show("\"Fortune's Foundation\" game screen is not visible.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
                Console.WriteLine("minor foundation =");
                foreach (List<Card> cell in boardState.MinorFoundationCells)
                {
                    foreach (Card card in cell)
                    {
                        Console.WriteLine(card.ToString());
                    }
                    Console.WriteLine("================================");
                    Console.WriteLine("");
                }

            return topScreen;
        }
                Console.WriteLine("lower major foundation =");
                foreach (Card card in boardState.LowerMajorFoundationCell)
                {
                    Console.WriteLine(card.ToString());
                }
                Console.WriteLine("");

        public static object GetUnknownController()
        {
            object gameScreen = GetMilanGameScreen();
            if (gameScreen == null) {
                return null;
            }
                Console.WriteLine("upper major foundation =");
                foreach (Card card in boardState.UpperMajorFoundationCell)
                {
                    Console.WriteLine(card.ToString());
                }
                Console.WriteLine("");

            Type milanGameScreenType = GameAssembly.GetType("Milan.GameScreen");
            MethodInfo getControllerMethod = milanGameScreenType.GetMethod("#=qt9YAlgdQ0iozKkKJ_tz6wg==", BindingFlags.NonPublic | BindingFlags.Instance);
            return getControllerMethod.Invoke(gameScreen, null);
        }
                Console.WriteLine("final major foundation =");
                foreach (Card card in boardState.FinalMajorFoundationCell)
                {
                    Console.WriteLine(card.ToString());
                }
                Console.WriteLine("");

        public static object GetBoardState()
        {
            object controller = GetUnknownController();
            if (controller == null) {
                return null;
                if (boardState.FreeCell is Card freeCell)
                {
                    Console.WriteLine("free cell = {0}", freeCell.ToString());
                }
                else
                {
                    Console.WriteLine("<nothing in free cell>");
                }
            }

            Type controllerType = GameAssembly.GetType("#=q0pk7ja$d6EGfjmD$pwqkUf_tDQr2NjwNEBTC6S3S6pw=");
            FieldInfo boardStateField = controllerType.GetField("#=qggOaeeswRk1DSyXZZarAEQ==", BindingFlags.Public | BindingFlags.Instance);
            return boardStateField.GetValue(controller);
        }

        public static IList GetTableauCells()
        {
            object boardState = GetBoardState();
            if (boardState == null) {
                return new List<object>();
            catch (MilanScreenNotVisibleException e)
            {
                MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            Type boardStateType = GameAssembly.GetType("Milan.BoardState");
            FieldInfo tableauItemsField = boardStateType.GetField("#=qzsOQ0cf1yO$i8Y9aA3nQSA==", BindingFlags.Public | BindingFlags.Instance);
            return tableauItemsField.GetValue(boardState) as IList;
        }

        public static IEnumerable GetCellIterator(object item)
        {
            Type itemType = GameAssembly.GetType("Milan.SolitaireItem");
            MethodInfo getIteratorMethod = itemType.GetMethod("#=q3T5hVjfGdlmlcVuQneFNdGZgSGtulISaq7a1odM4P8Q=", BindingFlags.Public | BindingFlags.Instance);
            return getIteratorMethod.Invoke(item, null) as IEnumerable;
        }

        public static int GetCardSuit(object item)
        {
            Type itemType = GameAssembly.GetType("Milan.SolitaireItem");
            FieldInfo field = itemType.GetField("#=qrVeI19YFRrL$TYnUhjJkGg==", BindingFlags.Public | BindingFlags.Instance);
            return (int) field.GetValue(item);
        }

        public static int GetCardValue(object item)
        {
            Type itemType = GameAssembly.GetType("Milan.SolitaireItem");
            FieldInfo field = itemType.GetField("#=qGa_rTwBFoHeQRSVVfaUd_Q==", BindingFlags.Public | BindingFlags.Instance);
            return (int)field.GetValue(item);
            return 1337;
        }
    }
}