- git status - pretty much self-explanatory, but basically shows you the current branch and whether it is up-to-date with the remote branch, as well as and untracked files or uncommitted changes.
- git add -A - adds files to the git index, -A is the option for "all files".
- git commit -m "commit message" - Commits the current changes with the commit message provided (-m is the message option).
Marcus Thorman's blog
mandag den 6. juni 2022
Adding a project to GitHub
onsdag den 1. juni 2022
Exploring the capabilities of .NET MAUI part 2: Saving and loading language selection in app preferences, skipping selection page if language was loaded from settings
In my last post I briefly touched on how you can implement localization resources in a .NET MAUI app.
By default, .NET will try to find localization resource that corresponds to that of the system that the app is running on, which is pretty convenient.
For this post, however, I wanted to implement a list of languages so that the user can select a language from a language selection page at first app launch, after which the selected language will be saved to app preferences, which is built directly into MAUI for all platforms that it supports.
So, I will implement a ContentPage which its own ViewModel (which is constructor-injected into the ContentPage using .NETs built-in depdendency-injection), which will serve as the first page that is shown only if the preferences contains no saved language.
First things first, I started with creating a simple Language model, which I placed in a "Models" folder in the solution:
namespace localization_demo.Models; public class Language { public Language(string identifier, string name) { Identifier = identifier; Name = name; } public string Identifier { get; set; } public string Name { get; set; } public override string ToString() { return Name; } }After this I began writing the code of the ViewModel, but explaining every line of it in text would make this post a bit longer than I would like, so I've instead placed some comments explaining the basic idea:
namespace localization_demo.ViewModels; public class ChooseLanguageViewModel { public ChooseLanguageViewModel() { // instantiate ObservableCollection of Language objects Languages = new ObservableCollection<Language> { new Language("en-US", "English"), new Language("da-DK", "Danish"), }; // try to load saved language LoadSavedLanguagePreferences(); } // this property will serve as the collection which we will show in our contentpage public ObservableCollection<Language> Languages { get; private set; } // contains the current selection private Language _selectedLanguage; public Language SelectedLanguage { get => _selectedLanguage; set { _selectedLanguage = value; SetLanguage(); } } private void SetLanguage() { SaveLanguagePreferences(SelectedLanguage); // change locale: Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(SelectedLanguage.Identifier); Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(SelectedLanguage.Identifier); CultureInfo.CurrentCulture = new System.Globalization.CultureInfo(SelectedLanguage.Identifier); CultureInfo.CurrentUICulture = new System.Globalization.CultureInfo(SelectedLanguage.Identifier); // continue GoToMainPage(); } private void LoadSavedLanguagePreferences() { if (!Preferences.ContainsKey("locale")) { return; } var savedLocale = Preferences.Get("locale", ""); if (string.IsNullOrEmpty(savedLocale)) { return; } var correspondingLanguage = Languages.Where(x => string.Compare(x.Identifier, savedLocale) == 0); if (correspondingLanguage.Count() != 1) { // something went wrong here return; } SelectedLanguage = correspondingLanguage.First(); } private void SaveLanguagePreferences(Language language) { if (language == null) { return; } // check if preferences is already set if (string.Compare(Preferences.Get("locale", ""), language.Identifier) == 0) { return; } Preferences.Set("locale", language.Identifier); } // //// does not preserve backstack private async void GoToMainPage() => await Shell.Current.GoToAsync($"////{nameof(MainPage)}"); }And then I implemented the ContentPage like so, notice that it sets BindingContext in the constructor to the injected ViewModel in order to bind to objects in xml etc:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:vm="clr-namespace:localization_demo.ViewModels" xmlns:model="clr-namespace:localization_demo.Models" x:DataType="vm:ChooseLanguageViewModel" x:Class="localization_demo.ChooseLanguagePage" xmlns:res="clr-namespace:localization_demo.Resources.Strings"> <VerticalStackLayout Spacing="25" Padding="30" HorizontalOptions="Center"> <Label Text="{x:Static res:AppResource.Choose_language}" SemanticProperties.HeadingLevel="Level1" FontSize="32" HorizontalOptions="Center" /> <CollectionView ItemsSource="{Binding Languages}" SelectionMode="Single" SelectedItem="{Binding SelectedLanguage}" > <CollectionView.ItemTemplate> <DataTemplate x:DataType="model:Language"> <Grid Padding="10"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Label Grid.Column="1" Text="{Binding Name}" FontSize="24" /> </Grid> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </VerticalStackLayout> </ContentPage>
using localization_demo.ViewModels; namespace localization_demo; public partial class ChooseLanguagePage : ContentPage { private readonly ChooseLanguageViewModel _viewModel; public ChooseLanguagePage(ChooseLanguageViewModel viewModel) { InitializeComponent(); BindingContext = _viewModel = viewModel; } }However, in order to use dependency-injection in this way, we need to register the types in the dependency-injection system in the CreateMauiApp method, so it ends up looking like this:
public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }); builder.Services.AddSingleton<ChooseLanguageViewModel>(); builder.Services.AddTransient<ChooseLanguagePage>(); return builder.Build(); } }And then lastly, we need to add the new ChooseLanguagePage to our AppShell as a ShellItem, where we also specify the route to it, like so:
<?xml version="1.0" encoding="UTF-8" ?> <Shell x:Class="localization_demo.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:localization_demo" xmlns:res="clr-namespace:localization_demo.Resources.Strings" Shell.FlyoutBehavior="Disabled"> <ShellContent ContentTemplate="{DataTemplate local:ChooseLanguagePage}" Route="ChooseLanguagePage" /> <ShellContent ContentTemplate="{DataTemplate local:MainPage}" Route="MainPage" /> </Shell>Now this in by no means a pretty GUI or anything, but the results should be sufficient in terms of being a PoC:
After selecting "Danish", we are redirected to the MainPage of the template app, with out localized strings:
fredag den 20. maj 2022
Exploring the capabilities of .NET MAUI part 1: Localization in .NET MAUI
Note: As of writing this, you have to install the Visual Studio Preview in order to develop .NET MAUI apps.
After creating a new project from a template, I like to build and run it as is, just to ensure that everything is working ok. The default MAUI template app is just a basic hello world with a button that increases a counter and updates the "Current count..." label text to reflect the counter, it looks like this:
We need to add a few resource files that contain the localized strings, and I chose to create a new folder in the Resources directory of my project, called Strings, so as to contain the localization strings (is handy if you support a lot of languages) to which I add one Resources File per language I want to support.
I want to support English and Danish in my app, so after adding adding AppResource.resx for the default English localization, and AppResource.da.resx for the Danish localization, we should have something similar to this:
Let's start with making the default language resource file - Visual Studio provides us with an easy way to insert strings to this resource file, so now we just want to move all the strings from the default MAUI template app into our default AppResource.resx file, like so:
After moving all the strings, we can now access the strings by referencing the resource file's namespace in xaml and access the strings directly, like so:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="localization_demo.MainPage" xmlns:res="clr-namespace:localization_demo.Resources.Strings"> <!-- here we declare the namespace that contains the strings --> <ScrollView> <VerticalStackLayout Spacing="25" Padding="30"> <Label Text="{x:Static res:AppResource.Hello_World}" SemanticProperties.HeadingLevel="Level1" FontSize="32" HorizontalOptions="Center" /> <Label Text="{x:Static res:AppResource.Welcome}" SemanticProperties.HeadingLevel="Level1" SemanticProperties.Description="{x:Static res:AppResource.Welcome_Desc}" FontSize="18" HorizontalOptions="Center" /> <Label Text="" FontSize="18" FontAttributes="Bold" x:Name="CounterLabel" HorizontalOptions="Center" /> <Button Text="{x:Static res:AppResource.Click_me}" FontAttributes="Bold" SemanticProperties.Hint="{x:Static res:AppResource.Click_me_Hint}" Clicked="OnCounterClicked" HorizontalOptions="Center" /> <Image Source="dotnet_bot.png" SemanticProperties.Description="{x:Static res:AppResource.dotnet_bot_png_Desc}" WidthRequest="250" HeightRequest="310" HorizontalOptions="Center" /> </VerticalStackLayout> </ScrollView> </ContentPage>
The CounterLabel text is empty in the xaml because I set it programmatically in the MainPage constructor:
using localization_demo.Resources.Strings; namespace localization_demo; public partial class MainPage : ContentPage { int count = 0; public MainPage() { InitializeComponent(); UpdateCounterLabelText(); // set CounterLabel text } private void UpdateCounterLabelText() { CounterLabel.Text = $"{AppResource.Current_count} {count}"; } private void OnCounterClicked(object sender, EventArgs e) { count++; UpdateCounterLabelText(); SemanticScreenReader.Announce(CounterLabel.Text); } }
We can add the same strings to the AppResource.da.resx file and translate them like so:
Which enables us to switch the resource using the Thread.CurrentThread.CurrentCulture and Thread.CurrentThread.CurrentUICulture APIs provided by .NET. For illustrative purposes, I modified the AppShell constructor to change locale to Danish:
public AppShell() { // change locale to da-DK: Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("da-DK"); Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("da-DK"); InitializeComponent(); }
lørdag den 16. juni 2012
CSGO: Chickens
Chickens have 28 hitboxes:
enum EChickenHitboxes { HITBOX_CHICKEN_PELVIS, HITBOX_CHICKEN_LEFTKNEE, HITBOX_CHICKEN_LEFTANKLE, HITBOX_CHICKEN_LEFTBACKTOE, HITBOX_CHICKEN_LEFTTOE, HITBOX_CHICKEN_RIGHTKNEE, HITBOX_CHICKEN_RIGHTANKLE, HITBOX_CHICKEN_RIGHTBACKTOE, HITBOX_CHICKEN_RIGHTTOE, HITBOX_CHICKEN_TAIL, HITBOX_CHICKEN_TAIL1, HITBOX_CHICKEN_TAIL2, HITBOX_CHICKEN_CLAVICAL, // gg misspell valve? HITBOX_CHICKEN_RIGHTBREAST, HITBOX_CHICKEN_RIGHTSHOULDER, HITBOX_CHICKEN_RIGHTELBOW, HITBOX_CHICKEN_RIGHTWRIST, HITBOX_CHICKEN_RIGHTTIP, HITBOX_CHICKEN_LEFTBREAST, HITBOX_CHICKEN_LEFTSHOULDER, HITBOX_CHICKEN_LEFTELBOW, HITBOX_CHICKEN_LEFTWRIST, HITBOX_CHICKEN_LEFTTIP, HITBOX_CHICKEN_NECK1, HITBOX_CHICKEN_NECK2, HITBOX_CHICKEN_NECK3, HITBOX_CHICKEN_HEAD, HITBOX_CHICKEN_WOBBLER };
The chickens all have the clientclass CDynamicProp, and you can check wether it's a chicken or not by checking model name; Ex:
bool CEntity::IsChicken( void ) { const model_t *model = GetModel(); if ( !model ) { LOG_ERROR( "model_t was 0" ); return false; } studiohdr_t *studioHeader = g_InterfaceManager->ModelInfo()->GetVTable<IVModelInfo>()->GetStudiomodel( model ); if ( !studioHeader ) { LOG_ERROR( "studiohdr_t was 0" ); return false; } return strstr( studioHeader->name, "chicken" ); }The pelvis hitbox is very big, almost like a bounding box tbh. and the height of the chickens hull is ~23;
A video of the result:
torsdag den 14. juni 2012
Networked Variables in the Source engine
netvars.h:
#pragma once #include "main.h" class CNetworkedVariableManager { public: // stores all tables, and all props inside those CNetworkedVariableManager( void ); // calls GetProp wrapper to get the absolute offset of the prop int GetOffset( const char *tableName, const char *propName ); // calls GetProp wrapper to get prop and sets the proxy of the prop bool HookProp( const char *tableName, const char *propName, RecvVarProxyFn function ); private: // wrapper so we can use recursion without too much performance loss int GetProp( const char *tableName, const char *propName, RecvProp **prop = 0 ); // uses recursion to return a the relative offset to the given prop and sets the prop param int GetProp( RecvTable *recvTable, const char *propName, RecvProp **prop = 0 ); RecvTable *GetTable( const char *tableName ); std::vector<RecvTable*> m_tables; }; extern CNetworkedVariableManager *g_NetworkedVariableManager;
netvars.cpp:
#include "main.h" CNetworkedVariableManager *g_NetworkedVariableManager = 0; CNetworkedVariableManager::CNetworkedVariableManager( void ) { m_tables.clear(); ClientClass *clientClass = g_InterfaceManager->Client()->GetOriginalMethod<GetAllClasses_t>( INDEX_GETALLCLASSES )( g_InterfaceManager->Client()->thisptr() ); if ( !clientClass ) { LOG_ERROR( "ClientClass was not found" ); return; } while ( clientClass ) { RecvTable *recvTable = clientClass->m_pRecvTable; m_tables.push_back( recvTable ); clientClass = clientClass->m_pNext; } } // calls GetProp wrapper to get the absolute offset of the prop int CNetworkedVariableManager::GetOffset( const char *tableName, const char *propName ) { int offset = GetProp( tableName, propName ); if ( !offset ) { LOG_ERROR( "Failed to find offset for prop: %s from table: %s", propName, tableName ); return 0; } return offset; } // calls GetProp wrapper to get prop and sets the proxy of the prop bool CNetworkedVariableManager::HookProp( const char *tableName, const char *propName, RecvVarProxyFn function ) { RecvProp *recvProp = 0; GetProp( tableName, propName, &recvProp ); if ( !recvProp ) { LOG_ERROR( "Failed to hook prop: %s from table: %s", propName, tableName ); return false; } recvProp->m_ProxyFn = function; return true; } // wrapper so we can use recursion without too much performance loss int CNetworkedVariableManager::GetProp( const char *tableName, const char *propName, RecvProp **prop ) { RecvTable *recvTable = GetTable( tableName ); if ( !recvTable ) { LOG_ERROR( "Failed to find table: %s", tableName ); return 0; } int offset = GetProp( recvTable, propName, prop ); if ( !offset ) { LOG_ERROR( "Failed to find prop: %s from table: %s", propName, tableName ); return 0; } return offset; } // uses recursion to return a the relative offset to the given prop and sets the prop param int CNetworkedVariableManager::GetProp( RecvTable *recvTable, const char *propName, RecvProp **prop ) { int extraOffset = 0; for ( int i = 0; i < recvTable->m_nProps; ++i ) { RecvProp *recvProp = &recvTable->m_pProps[i]; RecvTable *child = recvProp->m_pDataTable; if ( child && ( child->m_nProps > 0 ) ) { int tmp = GetProp( child, propName, prop ); if ( tmp ) { extraOffset += ( recvProp->m_Offset + tmp ); } } if ( stricmp( recvProp->m_pVarName, propName ) ) { continue; } if ( prop ) { *prop = recvProp; } return ( recvProp->m_Offset + extraOffset ); } return extraOffset; } RecvTable *CNetworkedVariableManager::GetTable( const char *tableName ) { if ( m_tables.empty() ) { LOG_ERROR( "Failed to find table: %s (m_tables is empty)", tableName ); return 0; } for each ( RecvTable *table in m_tables ) { if ( !table ) { continue; } if ( stricmp( table->m_pNetTableName, tableName ) == 0 ) { return table; } } return 0; }
example usages:
// we need to construct this manually g_NetworkedVariableManager = new CNetworkedVariableManager(); // hook netvar proxy g_NetworkedVariableManager->HookProp( "DT_CSPlayer", "m_angEyeAngles[0]", CSPlayer_EyeAnglesX );
Vector CPlayer::GetViewOffset( void ) { static int offset = g_NetworkedVariableManager->GetOffset( "DT_BasePlayer", "m_vecViewOffset[0]" ); return *Member<Vector*>( this, offset ); }
lørdag den 9. juni 2012
CTraceFilterSkipTwoEntities
This isn't anything special, but since there always seems to be alot of drama going on when there has been an update, I figured I'll post some code here and simple instructions on how to find it. If you will be using Olly, you need the SigMaker plugin, by p47r!ck: Sig Maker 0.4
This tracefilter(CTraceFilterSkipTwoEntities) which is used in peoples tracing code, can be found alot of places, but my preferred location is inside the CSPlayer::FireBullet function, which contains a neat little string which we can follow: "gunshotsplash".
The call to CTraceFilterSkipTwoEntities's constructor is almost at the top of the function and should look something like this(raw copy paste from Olly):
54227F7C 6A 00 PUSH 0 54227F7E 57 PUSH EDI 54227F7F 56 PUSH ESI 54227F80 8D8D 9CFDFFFF LEA ECX,DWORD PTR SS:[EBP-264] 54227F86 E8 E54BFCFF CALL client.541ECB70Just follow that call and make a signature/get the relative offset for it. Please note that this is achievable in IDA aswell, but Olly is my preferred tool.
void Aimbot::GetTraceFilterSkipTwoEntities( CSPlayer *first, CSPlayer *second, DWORD* thisptr ) { typedef void ( __thiscall *TraceFilterSkipTwoEntities_t )( void *thisptr, const CSPlayer *first, const CSPlayer *second, int group ); static TraceFilterSkipTwoEntities_t traceFilterskiptwoentities = (TraceFilterSkipTwoEntities_t)memory::FindPattern( GetModuleHandle( "client.dll" ), "\x55\x8B\xEC\x8B\x55\x10\x8B\xC1\x8B\x4D\x08\x89\x48\x04\x8B\x4D\x0C\x89\x50\x08\xC7\x40" ); if ( !traceFilterskiptwoentities ) { LOG_ERROR( "TraceFilterSkipTwoEntities not found" ); } traceFilterskiptwoentities( thisptr, first, second, 0 ); }ex. usage:
DWORD traceFilter[5]; // 20 or 0x14 bytes GetTraceFilterSkipTwoEntities( m_localPlayer, skipPlayer, traceFilter ); TraceRay( ray, 0x4600400B, (ITraceFilter*)&traceFilter, &traceData );